From c83f16dad69bebd121d0512902b414362e218d75 Mon Sep 17 00:00:00 2001 From: Kevin Turner <83819+keturn@users.noreply.github.com> Date: Sat, 11 Jun 2022 14:37:33 -0700 Subject: [PATCH 001/100] test(StorageManager): convert TTE tests to MTE Fixes #5032 --- .../internal/StorageManagerTest.java | 200 ++++++++---------- 1 file changed, 85 insertions(+), 115 deletions(-) diff --git a/engine-tests/src/test/java/org/terasology/engine/persistence/internal/StorageManagerTest.java b/engine-tests/src/test/java/org/terasology/engine/persistence/internal/StorageManagerTest.java index d7f20afd185..1702d4d0ab4 100644 --- a/engine-tests/src/test/java/org/terasology/engine/persistence/internal/StorageManagerTest.java +++ b/engine-tests/src/test/java/org/terasology/engine/persistence/internal/StorageManagerTest.java @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 package org.terasology.engine.persistence.internal; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import org.joml.Vector3f; import org.joml.Vector3i; @@ -11,58 +12,48 @@ import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentMatchers; import org.mockito.junit.jupiter.MockitoExtension; -import org.mockito.junit.jupiter.MockitoSettings; -import org.mockito.quality.Strictness; -import org.terasology.engine.TerasologyTestingEnvironment; +import org.terasology.engine.config.Config; +import org.terasology.engine.config.SystemConfig; +import org.terasology.engine.context.Context; +import org.terasology.engine.core.GameEngine; import org.terasology.engine.core.PathManager; import org.terasology.engine.core.bootstrap.EntitySystemSetupUtil; import org.terasology.engine.core.module.ModuleManager; +import org.terasology.engine.core.subsystem.EngineSubsystem; import org.terasology.engine.entitySystem.entity.EntityRef; import org.terasology.engine.entitySystem.entity.internal.EngineEntityManager; +import org.terasology.engine.integrationenvironment.jupiter.IntegrationEnvironment; +import org.terasology.engine.integrationenvironment.jupiter.MTEExtension; import org.terasology.engine.logic.location.LocationComponent; -import org.terasology.engine.network.Client; -import org.terasology.engine.network.ClientComponent; +import org.terasology.engine.logic.players.LocalPlayer; import org.terasology.engine.network.NetworkMode; import org.terasology.engine.network.NetworkSystem; import org.terasology.engine.persistence.ChunkStore; import org.terasology.engine.persistence.PlayerStore; import org.terasology.engine.persistence.StorageManager; -import org.terasology.engine.recording.CharacterStateEventPositionMap; -import org.terasology.engine.recording.DirectionAndOriginPosRecorderList; import org.terasology.engine.recording.RecordAndReplayCurrentStatus; import org.terasology.engine.recording.RecordAndReplaySerializer; import org.terasology.engine.recording.RecordAndReplayUtils; -import org.terasology.engine.recording.RecordedEventStore; import org.terasology.engine.registry.CoreRegistry; -import org.terasology.engine.world.WorldProvider; +import org.terasology.engine.registry.In; import org.terasology.engine.world.block.Block; import org.terasology.engine.world.block.BlockManager; -import org.terasology.engine.world.block.family.SymmetricFamily; -import org.terasology.engine.world.block.loader.BlockFamilyDefinition; -import org.terasology.engine.world.block.loader.BlockFamilyDefinitionData; import org.terasology.engine.world.chunks.Chunk; import org.terasology.engine.world.chunks.ChunkProvider; import org.terasology.engine.world.chunks.blockdata.ExtraBlockDataManager; import org.terasology.engine.world.chunks.internal.ChunkImpl; -import org.terasology.engine.world.internal.WorldInfo; -import org.terasology.gestalt.assets.ResourceUrn; -import org.terasology.gestalt.assets.management.AssetManager; import org.terasology.gestalt.module.ModuleEnvironment; import org.terasology.joml.geom.AABBfc; -import org.terasology.reflection.TypeRegistry; import org.terasology.unittest.stubs.EntityRefComponent; import org.terasology.unittest.stubs.StringComponent; -import java.lang.reflect.Method; import java.nio.file.Path; import java.util.List; -import java.util.UUID; +import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -72,21 +63,20 @@ import static org.mockito.Mockito.when; @TestMethodOrder(MethodOrderer.OrderAnnotation.class) -@Tag("TteTest") -@ExtendWith(MockitoExtension.class) -// TODO: Re-evaluate Mockito strictness settings after replacing TerasologyTestingEnvironment (#5010) -// Mockito is currently in LENIENT mode because some of the mocked objects or methods are only used -// by a subset of the tests. -@MockitoSettings(strictness = Strictness.LENIENT) -public class StorageManagerTest extends TerasologyTestingEnvironment { - public static final String PLAYER_ID = "someId"; +@Tag("MteTest") +@ExtendWith({MockitoExtension.class, MTEExtension.class}) +@IntegrationEnvironment( + networkMode = NetworkMode.NONE, // A mode with a LocalPlayer. + subsystem = StorageManagerTest.EnableWritingSaveGames.class +) +public class StorageManagerTest { public static final Vector3ic CHUNK_POS = new Vector3i(1, 2, 3); + @In + EngineEntityManager entityManager; + + private String playerId; private ModuleEnvironment moduleEnvironment; - private ReadWriteStorageManager esm; - private EngineEntityManager entityManager; - private BlockManager blockManager; - private ExtraBlockDataManager extraDataManager; private Block testBlock; private Block testBlock2; private EntityRef character; @@ -96,125 +86,89 @@ public class StorageManagerTest extends TerasologyTestingEnvironment { private RecordAndReplayCurrentStatus recordAndReplayCurrentStatus; @BeforeEach - public void setup(TestInfo testInfo) throws Exception { - super.setup(); + void setupLocals( + StorageManager storageManager, LocalPlayer player, NetworkSystem network, BlockManager blockManager, + ModuleManager moduleManager, Config config, Context context) { + assertThat(storageManager).isInstanceOf(ReadWriteStorageManager.class); + context.put(ReadWriteStorageManager.class, (ReadWriteStorageManager) storageManager); - String saveName = "testSave-" + testInfo.getTestMethod().map(Method::getName) - .orElseGet(() -> UUID.randomUUID().toString()); - savePath = PathManager.getInstance().getSavePath(saveName); + savePath = getSavePath(config); assertWithMessage("Leftover files in %s", savePath) .that(savePath.resolve("global.dat").toFile().exists()) .isFalse(); - entityManager = context.get(EngineEntityManager.class); - moduleEnvironment = mock(ModuleEnvironment.class); - blockManager = context.get(BlockManager.class); - extraDataManager = context.get(ExtraBlockDataManager.class); - - ModuleManager moduleManager = mock(ModuleManager.class); - - when(moduleManager.getEnvironment()).thenReturn(moduleEnvironment); - - RecordedEventStore recordedEventStore = new RecordedEventStore(); - recordAndReplayUtils = new RecordAndReplayUtils(); - CharacterStateEventPositionMap characterStateEventPositionMap = new CharacterStateEventPositionMap(); - DirectionAndOriginPosRecorderList directionAndOriginPosRecorderList = new DirectionAndOriginPosRecorderList(); - recordAndReplaySerializer = new RecordAndReplaySerializer(entityManager, recordedEventStore, - recordAndReplayUtils, characterStateEventPositionMap, directionAndOriginPosRecorderList, - moduleManager, mock(TypeRegistry.class)); - recordAndReplayCurrentStatus = context.get(RecordAndReplayCurrentStatus.class); - - - esm = new ReadWriteStorageManager(savePath, moduleEnvironment, entityManager, blockManager, extraDataManager, - false, recordAndReplaySerializer, recordAndReplayUtils, recordAndReplayCurrentStatus); - context.put(StorageManager.class, esm); - - this.character = entityManager.create(); - Client client = createClientMock(PLAYER_ID, character); - NetworkSystem networkSystem = mock(NetworkSystem.class); - when(networkSystem.getMode()).thenReturn(NetworkMode.NONE); - when(networkSystem.getPlayers()).thenReturn(List.of(client)); - context.put(NetworkSystem.class, networkSystem); - - AssetManager assetManager = context.get(AssetManager.class); - BlockFamilyDefinitionData data = new BlockFamilyDefinitionData(); - data.setBlockFamily(SymmetricFamily.class); - assetManager.loadAsset(new ResourceUrn("test:testblock"), data, BlockFamilyDefinition.class); - assetManager.loadAsset(new ResourceUrn("test:testblock2"), data, BlockFamilyDefinition.class); - testBlock = context.get(BlockManager.class).getBlock("test:testblock"); - testBlock2 = context.get(BlockManager.class).getBlock("test:testblock2"); - - context.put(ChunkProvider.class, mock(ChunkProvider.class)); - WorldProvider worldProvider = mock(WorldProvider.class); - when(worldProvider.getWorldInfo()).thenReturn(new WorldInfo()); - context.put(WorldProvider.class, worldProvider); - } + character = player.getCharacterEntity(); + assertThat(character).isNotEqualTo(EntityRef.NULL); + + var clients = ImmutableList.copyOf(network.getPlayers()); + assertThat(clients).hasSize(1); + playerId = clients.get(0).getId(); - private Client createClientMock(String clientId, EntityRef charac) { - EntityRef clientEntity = createClientEntity(charac); - Client client = mock(Client.class); - when(client.getEntity()).thenReturn(clientEntity); - when(client.getId()).thenReturn(clientId); - return client; + testBlock = blockManager.getBlock("test:testblock"); + testBlock2 = blockManager.getBlock("test:testblock2"); + + moduleEnvironment = moduleManager.getEnvironment(); } - private EntityRef createClientEntity(EntityRef charac) { - ClientComponent clientComponent = new ClientComponent(); - clientComponent.local = true; - clientComponent.character = charac; - return entityManager.create(clientComponent); + Path getSavePath(Config config) { + // TODO: add a more direct way of inspecting StorageManager's path. + // This way of getting the game title (and thus the path) is a bit brittle, + // because world title and game title are not _required_ to be identical. + String gameTitle = config.getWorldGeneration().getWorldTitle(); + return PathManager.getInstance().getSavePath(gameTitle); } @Test @Order(1) - public void testGetUnstoredPlayerReturnsNewStor() { - PlayerStore store = esm.loadPlayerStore(PLAYER_ID); + public void testGetUnstoredPlayerReturnsNewStor(StorageManager esm) { + PlayerStore store = esm.loadPlayerStore(playerId); assertNotNull(store); assertEquals(new Vector3f(), store.getRelevanceLocation()); assertFalse(store.hasCharacter()); - assertEquals(PLAYER_ID, store.getId()); + assertEquals(playerId, store.getId()); } @Test - public void testStoreAndRestoreOfPlayerWithoutCharacter() { + public void testStoreAndRestoreOfPlayerWithoutCharacter(StorageManager esm) { // remove character from player: character.destroy(); esm.waitForCompletionOfPreviousSaveAndStartSaving(); esm.finishSavingAndShutdown(); - PlayerStore restoredStore = esm.loadPlayerStore(PLAYER_ID); + PlayerStore restoredStore = esm.loadPlayerStore(playerId); assertNotNull(restoredStore); assertFalse(restoredStore.hasCharacter()); assertEquals(new Vector3f(), restoredStore.getRelevanceLocation()); } @Test - public void testPlayerRelevanceLocationSurvivesStorage() { + public void testPlayerRelevanceLocationSurvivesStorage(StorageManager esm) { Vector3f loc = new Vector3f(1, 2, 3); character.addComponent(new LocationComponent(loc)); esm.waitForCompletionOfPreviousSaveAndStartSaving(); esm.finishSavingAndShutdown(); - PlayerStore restored = esm.loadPlayerStore(PLAYER_ID); + PlayerStore restored = esm.loadPlayerStore(playerId); assertEquals(loc, restored.getRelevanceLocation()); } @Test - public void testCharacterSurvivesStorage() { + public void testCharacterSurvivesStorage(StorageManager esm) { esm.waitForCompletionOfPreviousSaveAndStartSaving(); esm.finishSavingAndShutdown(); - PlayerStore restored = esm.loadPlayerStore(PLAYER_ID); + PlayerStore restored = esm.loadPlayerStore(playerId); restored.restoreEntities(); assertTrue(restored.hasCharacter()); assertEquals(character, restored.getCharacter()); } @Test - public void testGlobalEntitiesStoredAndRestored() throws Exception { + public void testGlobalEntitiesStoredAndRestored( + ReadWriteStorageManager esm, BlockManager blockManager, ExtraBlockDataManager extraDataManager, Context context) throws Exception { EntityRef entity = entityManager.create(new StringComponent("Test")); long entityId = entity.getId(); @@ -226,7 +180,7 @@ public void testGlobalEntitiesStoredAndRestored() throws Exception { EngineEntityManager newEntityManager = context.get(EngineEntityManager.class); StorageManager newSM = new ReadWriteStorageManager(savePath, moduleEnvironment, newEntityManager, blockManager, - extraDataManager, false, recordAndReplaySerializer, recordAndReplayUtils, recordAndReplayCurrentStatus); + extraDataManager, esm.isStoreChunksInZips(), recordAndReplaySerializer, recordAndReplayUtils, recordAndReplayCurrentStatus); newSM.loadGlobalStore(); List entities = Lists.newArrayList(newEntityManager.getEntitiesWith(StringComponent.class)); @@ -236,7 +190,8 @@ public void testGlobalEntitiesStoredAndRestored() throws Exception { @Test - public void testReferenceRemainsValidOverStorageRestoral() throws Exception { + public void testReferenceRemainsValidOverStorageRestoral( + StorageManager esm, BlockManager blockManager, ExtraBlockDataManager extraDataManager, Context context) throws Exception { EntityRef someEntity = entityManager.create(); character.addComponent(new EntityRefComponent(someEntity)); @@ -250,18 +205,19 @@ public void testReferenceRemainsValidOverStorageRestoral() throws Exception { extraDataManager, false, recordAndReplaySerializer, recordAndReplayUtils, recordAndReplayCurrentStatus); newSM.loadGlobalStore(); - PlayerStore restored = newSM.loadPlayerStore(PLAYER_ID); + PlayerStore restored = newSM.loadPlayerStore(playerId); restored.restoreEntities(); assertTrue(restored.getCharacter().getComponent(EntityRefComponent.class).entityRef.exists()); } @Test - public void testGetUnstoredChunkReturnsNothing() { - esm.loadChunkStore(CHUNK_POS); + public void testGetUnstoredChunkReturnsNothing(StorageManager esm) { + assertThat(esm.loadChunkStore(CHUNK_POS)).isNull(); } @Test - public void testStoreAndRestoreChunkStore() { + public void testStoreAndRestoreChunkStore( + StorageManager esm, BlockManager blockManager, ExtraBlockDataManager extraDataManager) { Chunk chunk = new ChunkImpl(CHUNK_POS, blockManager, extraDataManager); chunk.setBlock(0, 0, 0, testBlock); chunk.markReady(); @@ -280,18 +236,17 @@ public void testStoreAndRestoreChunkStore() { } @Test - public void testChunkSurvivesStorageSaveAndRestore() throws Exception { + public void testChunkSurvivesStorageSaveAndRestore( + ReadWriteStorageManager esm, BlockManager blockManager, ExtraBlockDataManager extraDataManager, + Context context) throws Exception { Chunk chunk = new ChunkImpl(CHUNK_POS, blockManager, extraDataManager); chunk.setBlock(0, 0, 0, testBlock); chunk.setBlock(0, 4, 2, testBlock2); chunk.markReady(); ChunkProvider chunkProvider = mock(ChunkProvider.class); when(chunkProvider.getAllChunks()).thenReturn(List.of(chunk)); - when(chunkProvider.getChunk(ArgumentMatchers.any(Vector3ic.class))).thenReturn(chunk); CoreRegistry.put(ChunkProvider.class, chunkProvider); - boolean storeChunkInZips = true; - esm.setStoreChunksInZips(storeChunkInZips); esm.waitForCompletionOfPreviousSaveAndStartSaving(); esm.finishSavingAndShutdown(); @@ -299,7 +254,7 @@ public void testChunkSurvivesStorageSaveAndRestore() throws Exception { EntitySystemSetupUtil.addEntityManagementRelatedClasses(context); EngineEntityManager newEntityManager = context.get(EngineEntityManager.class); StorageManager newSM = new ReadWriteStorageManager(savePath, moduleEnvironment, newEntityManager, blockManager, - extraDataManager, storeChunkInZips, recordAndReplaySerializer, recordAndReplayUtils, + extraDataManager, esm.isStoreChunksInZips(), recordAndReplaySerializer, recordAndReplayUtils, recordAndReplayCurrentStatus); newSM.loadGlobalStore(); @@ -312,7 +267,9 @@ public void testChunkSurvivesStorageSaveAndRestore() throws Exception { } @Test - public void testEntitySurvivesStorageInChunkStore() throws Exception { + public void testEntitySurvivesStorageInChunkStore( + ReadWriteStorageManager esm, BlockManager blockManager, ExtraBlockDataManager extraDataManager, + Context context) throws Exception { Chunk chunk = new ChunkImpl(CHUNK_POS, blockManager, extraDataManager); chunk.setBlock(0, 0, 0, testBlock); chunk.markReady(); @@ -336,7 +293,7 @@ public void testEntitySurvivesStorageInChunkStore() throws Exception { EntitySystemSetupUtil.addEntityManagementRelatedClasses(context); EngineEntityManager newEntityManager = context.get(EngineEntityManager.class); StorageManager newSM = new ReadWriteStorageManager(savePath, moduleEnvironment, newEntityManager, blockManager, - extraDataManager, false, recordAndReplaySerializer, recordAndReplayUtils, recordAndReplayCurrentStatus); + extraDataManager, esm.isStoreChunksInZips(), recordAndReplaySerializer, recordAndReplayUtils, recordAndReplayCurrentStatus); newSM.loadGlobalStore(); ChunkStore restored = newSM.loadChunkStore(CHUNK_POS); @@ -348,10 +305,23 @@ public void testEntitySurvivesStorageInChunkStore() throws Exception { @Test - public void testCanSavePlayerWithoutUnloading() { + public void testCanSavePlayerWithoutUnloading(StorageManager esm) { esm.waitForCompletionOfPreviousSaveAndStartSaving(); esm.finishSavingAndShutdown(); assertTrue(character.isActive()); } + + static class EnableWritingSaveGames implements EngineSubsystem { + + @Override + public String getName() { + return this.getClass().getCanonicalName(); + } + + @Override + public void initialise(GameEngine engine, Context rootContext) { + rootContext.getValue(SystemConfig.class).writeSaveGamesEnabled.set(true); + } + } } From 03dd618c1a43d8f528d2c40710b4424947a55041 Mon Sep 17 00:00:00 2001 From: Kevin Turner <83819+keturn@users.noreply.github.com> Date: Sun, 12 Jun 2022 17:10:44 -0700 Subject: [PATCH 002/100] test: add a subsystem with default naming to make custom test subsystems more concise --- .../subsystem/NonPlayerVisibleSubsystem.java | 16 ++++++++++++++++ .../CustomSubsystemTest.java | 13 ++----------- .../persistence/internal/StorageManagerTest.java | 10 ++-------- 3 files changed, 20 insertions(+), 19 deletions(-) create mode 100644 engine-tests/src/main/java/org/terasology/engine/core/subsystem/NonPlayerVisibleSubsystem.java diff --git a/engine-tests/src/main/java/org/terasology/engine/core/subsystem/NonPlayerVisibleSubsystem.java b/engine-tests/src/main/java/org/terasology/engine/core/subsystem/NonPlayerVisibleSubsystem.java new file mode 100644 index 00000000000..98cdfbfa4af --- /dev/null +++ b/engine-tests/src/main/java/org/terasology/engine/core/subsystem/NonPlayerVisibleSubsystem.java @@ -0,0 +1,16 @@ +// Copyright 2022 The Terasology Foundation +// SPDX-License-Identifier: Apache-2.0 + +package org.terasology.engine.core.subsystem; + +public abstract class NonPlayerVisibleSubsystem implements EngineSubsystem { + /** + * An unambiguous but messy name. + *

+ * Good enough for test subsystems. + */ + @Override + public String getName() { + return getClass().getCanonicalName(); + } +} diff --git a/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/CustomSubsystemTest.java b/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/CustomSubsystemTest.java index b68a529a423..471614765d4 100644 --- a/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/CustomSubsystemTest.java +++ b/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/CustomSubsystemTest.java @@ -10,7 +10,7 @@ import org.terasology.engine.context.Context; import org.terasology.engine.core.GameEngine; import org.terasology.engine.core.TerasologyEngine; -import org.terasology.engine.core.subsystem.EngineSubsystem; +import org.terasology.engine.core.subsystem.NonPlayerVisibleSubsystem; import org.terasology.engine.integrationenvironment.jupiter.IntegrationEnvironment; import org.terasology.engine.integrationenvironment.jupiter.MTEExtension; @@ -43,20 +43,11 @@ void testConfigurationBySubsystemInitialisation(PlayerConfig config) { * it's a convenient way to keep it close to the test code and still be something we can give * to an annotation. */ - static class MySubsystem implements EngineSubsystem { - + static class MySubsystem extends NonPlayerVisibleSubsystem { @Override public void initialise(GameEngine engine, Context rootContext) { var config = rootContext.getValue(PlayerConfig.class); config.playerName.set(PLAYER_NAME); } - - @Override - public String getName() { - // TODO: provide default implementation of EngineSubsystem.getName. - // The interface requires we implement this method, but test-only subsystems aren't - // player-visible. - return this.getClass().getSimpleName(); - } } } diff --git a/engine-tests/src/test/java/org/terasology/engine/persistence/internal/StorageManagerTest.java b/engine-tests/src/test/java/org/terasology/engine/persistence/internal/StorageManagerTest.java index 1702d4d0ab4..a3fa4dbeafd 100644 --- a/engine-tests/src/test/java/org/terasology/engine/persistence/internal/StorageManagerTest.java +++ b/engine-tests/src/test/java/org/terasology/engine/persistence/internal/StorageManagerTest.java @@ -22,7 +22,7 @@ import org.terasology.engine.core.PathManager; import org.terasology.engine.core.bootstrap.EntitySystemSetupUtil; import org.terasology.engine.core.module.ModuleManager; -import org.terasology.engine.core.subsystem.EngineSubsystem; +import org.terasology.engine.core.subsystem.NonPlayerVisibleSubsystem; import org.terasology.engine.entitySystem.entity.EntityRef; import org.terasology.engine.entitySystem.entity.internal.EngineEntityManager; import org.terasology.engine.integrationenvironment.jupiter.IntegrationEnvironment; @@ -312,13 +312,7 @@ public void testCanSavePlayerWithoutUnloading(StorageManager esm) { assertTrue(character.isActive()); } - static class EnableWritingSaveGames implements EngineSubsystem { - - @Override - public String getName() { - return this.getClass().getCanonicalName(); - } - + static class EnableWritingSaveGames extends NonPlayerVisibleSubsystem { @Override public void initialise(GameEngine engine, Context rootContext) { rootContext.getValue(SystemConfig.class).writeSaveGamesEnabled.set(true); From 4c595f9fee5af16cdda067497cc2c4cd528f348f Mon Sep 17 00:00:00 2001 From: "Josephine \"Niruandaleth\" Rueckert" Date: Sat, 3 Sep 2022 12:03:58 +0200 Subject: [PATCH 003/100] chore: prepare snapshot builds for 5.4.0-SNAPSHOT --- .../src/main/resources/org/terasology/unittest/module.txt | 2 +- engine/src/main/resources/org/terasology/engine/module.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/engine-tests/src/main/resources/org/terasology/unittest/module.txt b/engine-tests/src/main/resources/org/terasology/unittest/module.txt index c2611779862..4c9fd357c5f 100644 --- a/engine-tests/src/main/resources/org/terasology/unittest/module.txt +++ b/engine-tests/src/main/resources/org/terasology/unittest/module.txt @@ -1,6 +1,6 @@ { "id" : "unittest", - "version" : "5.3.0", + "version" : "5.4.0-SNAPSHOT", "displayName" : "Terasology Engine Test", "description" : "Engine unit test content" } diff --git a/engine/src/main/resources/org/terasology/engine/module.txt b/engine/src/main/resources/org/terasology/engine/module.txt index ce0f01cdabe..3544d9febb3 100644 --- a/engine/src/main/resources/org/terasology/engine/module.txt +++ b/engine/src/main/resources/org/terasology/engine/module.txt @@ -1,6 +1,6 @@ { "id" : "engine", - "version" : "5.3.0", + "version" : "5.4.0-SNAPSHOT", "displayName" : "Terasology Engine", "description" : "Core engine content" } From 68ddfff0fa689007500f0644aac2549699763b5b Mon Sep 17 00:00:00 2001 From: Kevin Turner <83819+keturn@users.noreply.github.com> Date: Wed, 8 Jun 2022 20:05:21 -0700 Subject: [PATCH 004/100] chore(StorageManager): replace TaskMaster with Reactor --- .../internal/ReadWriteStorageManager.java | 54 ++++++++----------- .../persistence/internal/SaveTransaction.java | 20 ++----- .../internal/SaveTransactionResult.java | 30 ----------- .../utilities/concurrency/AbstractTask.java | 12 ----- 4 files changed, 27 insertions(+), 89 deletions(-) delete mode 100644 engine/src/main/java/org/terasology/engine/persistence/internal/SaveTransactionResult.java delete mode 100644 engine/src/main/java/org/terasology/engine/utilities/concurrency/AbstractTask.java diff --git a/engine/src/main/java/org/terasology/engine/persistence/internal/ReadWriteStorageManager.java b/engine/src/main/java/org/terasology/engine/persistence/internal/ReadWriteStorageManager.java index 75dc06432cf..bca78f2009d 100644 --- a/engine/src/main/java/org/terasology/engine/persistence/internal/ReadWriteStorageManager.java +++ b/engine/src/main/java/org/terasology/engine/persistence/internal/ReadWriteStorageManager.java @@ -12,6 +12,7 @@ import org.terasology.engine.config.SystemConfig; import org.terasology.engine.config.UniverseConfig; import org.terasology.engine.core.ComponentSystemManager; +import org.terasology.engine.core.GameScheduler; import org.terasology.engine.core.PathManager; import org.terasology.engine.core.Time; import org.terasology.engine.core.module.ModuleManager; @@ -36,9 +37,6 @@ import org.terasology.engine.registry.CoreRegistry; import org.terasology.engine.rendering.opengl.ScreenGrabber; import org.terasology.engine.utilities.FilesUtil; -import org.terasology.engine.utilities.concurrency.ShutdownTask; -import org.terasology.engine.utilities.concurrency.Task; -import org.terasology.engine.utilities.concurrency.TaskMaster; import org.terasology.engine.world.block.BlockManager; import org.terasology.engine.world.block.family.BlockFamily; import org.terasology.engine.world.chunks.Chunk; @@ -53,6 +51,7 @@ import org.terasology.gestalt.module.ModuleEnvironment; import org.terasology.persistence.typeHandling.TypeHandlerLibrary; import org.terasology.protobuf.EntityData; +import reactor.core.publisher.Mono; import java.io.IOException; import java.nio.file.Files; @@ -70,7 +69,6 @@ public final class ReadWriteStorageManager extends AbstractStorageManager implements EntityDestroySubscriber, EntityChangeSubscriber, DelayedEntityRefFactory { private static final Logger logger = LoggerFactory.getLogger(ReadWriteStorageManager.class); - private final TaskMaster saveThreadManager; private final SaveTransactionHelper saveTransactionHelper; /** @@ -85,7 +83,7 @@ public final class ReadWriteStorageManager extends AbstractStorageManager private final ReadWriteLock worldDirectoryLock = new ReentrantReadWriteLock(true); private final Lock worldDirectoryReadLock = worldDirectoryLock.readLock(); private final Lock worldDirectoryWriteLock = worldDirectoryLock.writeLock(); - private SaveTransaction saveTransaction; + private Mono saveTransaction; private final Config config; private final SystemConfig systemConfig; @@ -131,7 +129,6 @@ public ReadWriteStorageManager(Path savePath, ModuleEnvironment environment, Eng this.privateEntityManager = createPrivateEntityManager(entityManager.getComponentLibrary()); Files.createDirectories(getStoragePathProvider().getStoragePathDirectory()); this.saveTransactionHelper = new SaveTransactionHelper(getStoragePathProvider()); - this.saveThreadManager = TaskMaster.createFIFOTaskMaster("Saving", 1); this.config = CoreRegistry.get(Config.class); this.systemConfig = CoreRegistry.get((SystemConfig.class)); this.entityRefReplacingComponentLibrary = privateEntityManager.getComponentLibrary() @@ -155,25 +152,11 @@ public void finishSavingAndShutdown() { if (recordAndReplayCurrentStatus.getStatus() == RecordAndReplayStatus.RECORDING) { recordAndReplayUtils.setShutdownRequested(true); } - saveThreadManager.shutdown(new ShutdownTask(), true); - checkSaveTransactionAndClearUpIfItIsDone(); - } - - private void checkSaveTransactionAndClearUpIfItIsDone() { if (saveTransaction != null) { - SaveTransactionResult result = saveTransaction.getResult(); - if (result != null) { - Throwable t = saveTransaction.getResult().getCatchedThrowable(); - if (t != null) { - throw new RuntimeException("Saving failed", t); - } - saveTransaction = null; - } - unloadedAndSavingChunkMap.clear(); + saveTransaction.block(); } } - private void addGlobalStoreBuilderToSaveTransaction(SaveTransactionBuilder transactionBuilder) { GlobalStoreBuilder globalStoreBuilder = new GlobalStoreBuilder(getEntityManager(), getPrefabSerializer()); transactionBuilder.setGlobalStoreBuilder(globalStoreBuilder); @@ -248,11 +231,9 @@ private void waitForCompletionOfPreviousSave() { if (recordAndReplayCurrentStatus.getStatus() == RecordAndReplayStatus.REPLAY_FINISHED) { recordAndReplayUtils.setShutdownRequested(true); //Important to trigger complete serialization in a recording } - if (saveTransaction != null && saveTransaction.getResult() == null) { - saveThreadManager.shutdown(new ShutdownTask(), true); - saveThreadManager.restart(); + if (saveTransaction != null) { + saveTransaction.block(); } - checkSaveTransactionAndClearUpIfItIsDone(); } private SaveTransaction createSaveTransaction() { @@ -391,7 +372,6 @@ public void update() { return; } - checkSaveTransactionAndClearUpIfItIsDone(); if (saveRequested) { startSaving(); } else if (isSavingNecessary()) { @@ -413,8 +393,11 @@ private void startSaving() { } saveRequested = false; - saveTransaction = createSaveTransaction(); - saveThreadManager.offer(saveTransaction); + saveTransaction = Mono.fromRunnable(createSaveTransaction()) + .subscribeOn(GameScheduler.parallel()) + .doFinally(unused -> this.saveComplete()) + .share(); + saveTransaction.subscribe(); if (recordAndReplayCurrentStatus.getStatus() == RecordAndReplayStatus.NOT_ACTIVATED) { saveGamePreviewImage(); @@ -436,8 +419,11 @@ private void startAutoSaving() { sys.preAutoSave(); } - saveTransaction = createSaveTransaction(); - saveThreadManager.offer(saveTransaction); + saveTransaction = Mono.fromRunnable(createSaveTransaction()) + .subscribeOn(GameScheduler.parallel()) + .doFinally(unused -> this.saveComplete()) + .share(); + saveTransaction.subscribe(); for (ComponentSystem sys : componentSystemManager.getAllSystems()) { sys.postAutoSave(); @@ -449,6 +435,12 @@ private void startAutoSaving() { logger.info("Auto Saving - Snapshot created: Writing phase starts"); } + private void saveComplete() { + saveTransaction = null; + unloadedAndSavingChunkMap.clear(); + unloadedAndSavingPlayerMap.clear(); + } + private boolean isSavingNecessary() { ChunkProvider chunkProvider = CoreRegistry.get(ChunkProvider.class); int unloadedChunkCount = unloadedAndUnsavedChunkMap.size(); @@ -482,7 +474,7 @@ private void saveGamePreviewImage() { @Override public boolean isSaving() { - return saveTransaction != null && saveTransaction.getResult() == null; + return saveTransaction != null; } @Override diff --git a/engine/src/main/java/org/terasology/engine/persistence/internal/SaveTransaction.java b/engine/src/main/java/org/terasology/engine/persistence/internal/SaveTransaction.java index d7b3b82e685..88bb32ff439 100644 --- a/engine/src/main/java/org/terasology/engine/persistence/internal/SaveTransaction.java +++ b/engine/src/main/java/org/terasology/engine/persistence/internal/SaveTransaction.java @@ -19,7 +19,6 @@ import org.terasology.engine.recording.RecordAndReplaySerializer; import org.terasology.engine.recording.RecordAndReplayStatus; import org.terasology.engine.recording.RecordAndReplayUtils; -import org.terasology.engine.utilities.concurrency.AbstractTask; import org.terasology.engine.world.chunks.Chunks; import org.terasology.engine.world.chunks.internal.ChunkImpl; import org.terasology.protobuf.EntityData; @@ -27,6 +26,7 @@ import java.io.BufferedOutputStream; import java.io.IOException; import java.io.OutputStream; +import java.io.UncheckedIOException; import java.net.URI; import java.nio.file.AccessDeniedException; import java.nio.file.AtomicMoveNotSupportedException; @@ -50,7 +50,7 @@ /** * Task that writes a previously created memory snapshot of the game to the disk. */ -public class SaveTransaction extends AbstractTask { +public class SaveTransaction implements Runnable { private static final Logger logger = LoggerFactory.getLogger(SaveTransaction.class); private static final ImmutableMap CREATE_ZIP_OPTIONS = ImmutableMap.of("create", "true", "encoding", "UTF-8"); @@ -58,7 +58,6 @@ public class SaveTransaction extends AbstractTask { private final Lock worldDirectoryWriteLock; private final EngineEntityManager privateEntityManager; private final EntitySetDeltaRecorder deltaToSave; - private volatile SaveTransactionResult result; // Unprocessed data to save: private final Map unloadedPlayers; @@ -112,8 +111,6 @@ public SaveTransaction(EngineEntityManager privateEntityManager, EntitySetDeltaR this.recordAndReplayCurrentStatus = recordAndReplayCurrentStatus; } - - @Override public String getName() { return "Saving"; } @@ -139,12 +136,11 @@ public void run() { saveGameManifest(); perpareChangesForMerge(); mergeChanges(); - result = SaveTransactionResult.createSuccessResult(); logger.info("Save game finished"); saveRecordingData(); - } catch (IOException | RuntimeException t) { + } catch (IOException t) { logger.error("Save game creation failed", t); - result = SaveTransactionResult.createFailureResult(t); + throw new UncheckedIOException("Save game creation failed", t); } } @@ -437,14 +433,6 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) } } - /** - * @return the result if there is one yet or null. This method returns the value of a volatile variable and - * can thus be used even from another thread. - */ - public SaveTransactionResult getResult() { - return result; - } - private void saveGameManifest() { try { Path path = storagePathProvider.getGameManifestTempPath(); diff --git a/engine/src/main/java/org/terasology/engine/persistence/internal/SaveTransactionResult.java b/engine/src/main/java/org/terasology/engine/persistence/internal/SaveTransactionResult.java deleted file mode 100644 index b6cd1a0efaa..00000000000 --- a/engine/src/main/java/org/terasology/engine/persistence/internal/SaveTransactionResult.java +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2021 The Terasology Foundation -// SPDX-License-Identifier: Apache-2.0 -package org.terasology.engine.persistence.internal; - -/** - * Represents the result of a {@link SaveTransaction} - */ -final class SaveTransactionResult { - private final Throwable catchedThrowable; - - private SaveTransactionResult(Throwable catchedThrowable) { - this.catchedThrowable = catchedThrowable; - } - - static SaveTransactionResult createSuccessResult() { - return new SaveTransactionResult(null); - } - - static SaveTransactionResult createFailureResult(Throwable catchedThrowable) { - return new SaveTransactionResult(catchedThrowable); - } - - public boolean isSuccess() { - return catchedThrowable == null; - } - - public Throwable getCatchedThrowable() { - return catchedThrowable; - } -} diff --git a/engine/src/main/java/org/terasology/engine/utilities/concurrency/AbstractTask.java b/engine/src/main/java/org/terasology/engine/utilities/concurrency/AbstractTask.java deleted file mode 100644 index f14c638b1cb..00000000000 --- a/engine/src/main/java/org/terasology/engine/utilities/concurrency/AbstractTask.java +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2021 The Terasology Foundation -// SPDX-License-Identifier: Apache-2.0 - -package org.terasology.engine.utilities.concurrency; - -public abstract class AbstractTask implements Task { - - @Override - public boolean isTerminateSignal() { - return false; - } -} From 4ad7b682942b5eb5d4cfa21c0cc24ab06f979794 Mon Sep 17 00:00:00 2001 From: jdrueckert Date: Sat, 5 Nov 2022 19:52:45 +0100 Subject: [PATCH 005/100] ci: add workflow to auto-add issues and PRs to projects (#5070) A new GitHub workflow based on https://github.com/marketplace/actions/assign-to-one-project that adds issues and PRs to project boards: - new issues and PRs to "New (Inbox)" column of https://github.com/orgs/MovingBlocks/projects/26/views/1 - issues and PRs newly labeled with `Type: Bug` to "Backlog" column of https://github.com/orgs/MovingBlocks/projects/26/views/1 - issues and PRs newly labeled with `Type: Refactoring` / `Type: Chore` / `Topic: Stabilization` and not `Type: Bug` to "Backlog" column of https://github.com/orgs/MovingBlocks/projects/25/views/1 - issues and PRs newly labeled with `Type: Improvement` and not `Topic: Stabilization` to "Backlog" column of https://github.com/orgs/MovingBlocks/projects/27/views/1 --- .github/workflows/project-autoadd.yml | 55 +++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 .github/workflows/project-autoadd.yml diff --git a/.github/workflows/project-autoadd.yml b/.github/workflows/project-autoadd.yml new file mode 100644 index 00000000000..c594331ab19 --- /dev/null +++ b/.github/workflows/project-autoadd.yml @@ -0,0 +1,55 @@ +name: Auto Assign to Projects + +on: + issues: + types: [opened, labeled] + pull_request: + types: [opened, labeled] +env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +jobs: + assign_one_project: + runs-on: ubuntu-latest + name: Assign to One Project + steps: + + - name: Assign NEW issues and issues to bug/inbox board's inbox + uses: srggrs/assign-one-project-github-action@1.2.1 + if: github.event.action == 'opened' + with: + project: 'https://github.com/srggrs/assign-one-project-github-action/projects/26' + column_name: 'New (Inbox)' + + - name: Assign issues and pull requests with `Type: Bug` label to bug board backlog + uses: srggrs/assign-one-project-github-action@1.2.1 + if: | + contains(github.event.issue.labels.*.name, 'Type: Bug') || + contains(github.event.pull_request.labels.*.name, 'Type: Bug') + with: + project: 'https://github.com/srggrs/assign-one-project-github-action/projects/26' + column_name: 'Backlog' + + - name: Assign stabilization / refactoring / maintenance issues and pull requests to stabilization board backlog + uses: srggrs/assign-one-project-github-action@1.2.1 + if: | + github.event.action != 'opened' && + ( contains(github.event.issue.labels.*.name, 'Type: Refactoring') || + contains(github.event.pull_request.labels.*.name, 'Type: Refactoring') || + contains(github.event.issue.labels.*.name, 'Type: Chore') || + contains(github.event.pull_request.labels.*.name, 'Type: Chore') || + contains(github.event.issue.labels.*.name, 'Topic: Stabilization') && !contains(github.event.issue.labels.*.name, 'Type: Bug') || + contains(github.event.pull_request.labels.*.name, 'Topic: Stabilization') && !contains(github.event.pull_request.labels.*.name, 'Type: Bug') ) + with: + project: 'https://github.com/srggrs/assign-one-project-github-action/projects/25' + column_name: 'Backlog' + + - name: Assign issues and pull requests with `Type: Improvement` label to feature board backlog + uses: srggrs/assign-one-project-github-action@1.2.1 + if: | + github.event.action != 'opened' && + contains(github.event.issue.labels.*.name, 'Type: Improvement') && !contains(github.event.issue.labels.*.name, 'Topic: Stabilization') || + contains(github.event.pull_request.labels.*.name, 'Type: Improvement') && !contains(github.event.pull_request.labels.*.name, 'Topic: Stabilization') + with: + project: 'https://github.com/srggrs/assign-one-project-github-action/projects/27' + column_name: 'Backlog' From 232909cb24ac9ba45117c082222e3c49687be748 Mon Sep 17 00:00:00 2001 From: jdrueckert Date: Sat, 5 Nov 2022 22:32:07 +0100 Subject: [PATCH 006/100] ci: add workflow autoclosing issues with unresponsive authors (#5071) A new GitHub workflow based on https://github.com/marketplace/actions/close-stale-issues that marks issues stale and eventually closes them if they need author input but their author doesn't respond. - only issues with label `Status: Needs Author Input` are affected - issues will be marked stale via the `Status: Stale` label and a comment if there is no activity for 30 days - issues will be closed as `not_planned` with a comment if there is no activity for 90 days after the issue was marked stale - PRs are not affected by this workflow --- .github/workflows/autoclose.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .github/workflows/autoclose.yml diff --git a/.github/workflows/autoclose.yml b/.github/workflows/autoclose.yml new file mode 100644 index 00000000000..b6e156cca11 --- /dev/null +++ b/.github/workflows/autoclose.yml @@ -0,0 +1,24 @@ +name: Close issues due to author inactivity +on: + schedule: + - cron: "30 15 * * 6" + +jobs: + close-issues: + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + steps: + - uses: actions/stale@v4 + with: + only-labels: "Status: Needs Author Input" + days-before-issue-stale: 30 + days-before-issue-close: 90 + stale-issue-label: "Status: Stale" + stale-issue-message: "This issue is stale because the author @${{ github.event.issue.user.login }} has not responded for 30 days." + close-issue-message: "This issue was closed because the author @${{ github.event.issue.user.login }} has not responded for 90 days since the issue was marked as stale." + close-issue-reason: "not_planned" + days-before-pr-stale: -1 + days-before-pr-close: -1 + repo-token: ${{ secrets.GITHUB_TOKEN }} From e8380cf4ea825b1cf3d51104c9dc357dced92e21 Mon Sep 17 00:00:00 2001 From: jdrueckert Date: Sun, 13 Nov 2022 12:23:52 +0100 Subject: [PATCH 007/100] ci: fix syntax errors in project-autoadd workflow --- .github/workflows/project-autoadd.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/project-autoadd.yml b/.github/workflows/project-autoadd.yml index c594331ab19..7931b1fd2cc 100644 --- a/.github/workflows/project-autoadd.yml +++ b/.github/workflows/project-autoadd.yml @@ -21,7 +21,7 @@ jobs: project: 'https://github.com/srggrs/assign-one-project-github-action/projects/26' column_name: 'New (Inbox)' - - name: Assign issues and pull requests with `Type: Bug` label to bug board backlog + - name: Assign bug report issues and pull requests to bug board backlog uses: srggrs/assign-one-project-github-action@1.2.1 if: | contains(github.event.issue.labels.*.name, 'Type: Bug') || @@ -44,7 +44,7 @@ jobs: project: 'https://github.com/srggrs/assign-one-project-github-action/projects/25' column_name: 'Backlog' - - name: Assign issues and pull requests with `Type: Improvement` label to feature board backlog + - name: Assign improvement issues and pull requests to feature board backlog uses: srggrs/assign-one-project-github-action@1.2.1 if: | github.event.action != 'opened' && From df98a45298acf75f73453f520868e276738f115b Mon Sep 17 00:00:00 2001 From: jdrueckert Date: Sat, 26 Nov 2022 12:34:11 +0100 Subject: [PATCH 008/100] ci: fix project-autoadd github action (#5080) A fix for the [`project-autoadd` github action](https://github.com/MovingBlocks/Terasology/blob/develop/.github/workflows/project-autoadd.yml) which failed because the utilized underlying action only supports classic projects but we're using v2 (beta) projects. The replacement action not only works but is a base action provided by GitHub which hopefully means better maintenance and higher security awareness. The action requires a token with control privileges for projects. I added such a token to the organization secrets and wrote a [maintainer guide](https://github.com/MovingBlocks/Terasology/wiki/Maintenance#how-to-fix-an-expired-github-action-token) on what to do in case the action starts failing because this token expired. --- .github/workflows/project-autoadd.yml | 94 ++++++++++++++------------- 1 file changed, 50 insertions(+), 44 deletions(-) diff --git a/.github/workflows/project-autoadd.yml b/.github/workflows/project-autoadd.yml index 7931b1fd2cc..5e685efc096 100644 --- a/.github/workflows/project-autoadd.yml +++ b/.github/workflows/project-autoadd.yml @@ -5,51 +5,57 @@ on: types: [opened, labeled] pull_request: types: [opened, labeled] -env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} jobs: - assign_one_project: + add_opened_to_inbox_board: + name: Add New Issue/PR to Terasology Inbox Board + if: github.event.action == 'opened' runs-on: ubuntu-latest - name: Assign to One Project steps: - - - name: Assign NEW issues and issues to bug/inbox board's inbox - uses: srggrs/assign-one-project-github-action@1.2.1 - if: github.event.action == 'opened' - with: - project: 'https://github.com/srggrs/assign-one-project-github-action/projects/26' - column_name: 'New (Inbox)' - - - name: Assign bug report issues and pull requests to bug board backlog - uses: srggrs/assign-one-project-github-action@1.2.1 - if: | - contains(github.event.issue.labels.*.name, 'Type: Bug') || - contains(github.event.pull_request.labels.*.name, 'Type: Bug') - with: - project: 'https://github.com/srggrs/assign-one-project-github-action/projects/26' - column_name: 'Backlog' - - - name: Assign stabilization / refactoring / maintenance issues and pull requests to stabilization board backlog - uses: srggrs/assign-one-project-github-action@1.2.1 - if: | - github.event.action != 'opened' && - ( contains(github.event.issue.labels.*.name, 'Type: Refactoring') || - contains(github.event.pull_request.labels.*.name, 'Type: Refactoring') || - contains(github.event.issue.labels.*.name, 'Type: Chore') || - contains(github.event.pull_request.labels.*.name, 'Type: Chore') || - contains(github.event.issue.labels.*.name, 'Topic: Stabilization') && !contains(github.event.issue.labels.*.name, 'Type: Bug') || - contains(github.event.pull_request.labels.*.name, 'Topic: Stabilization') && !contains(github.event.pull_request.labels.*.name, 'Type: Bug') ) - with: - project: 'https://github.com/srggrs/assign-one-project-github-action/projects/25' - column_name: 'Backlog' - - - name: Assign improvement issues and pull requests to feature board backlog - uses: srggrs/assign-one-project-github-action@1.2.1 - if: | - github.event.action != 'opened' && - contains(github.event.issue.labels.*.name, 'Type: Improvement') && !contains(github.event.issue.labels.*.name, 'Topic: Stabilization') || - contains(github.event.pull_request.labels.*.name, 'Type: Improvement') && !contains(github.event.pull_request.labels.*.name, 'Topic: Stabilization') - with: - project: 'https://github.com/srggrs/assign-one-project-github-action/projects/27' - column_name: 'Backlog' + - name: Add to inbox board + uses: actions/add-to-project@v0.3.0 + with: + project-url: 'https://github.com/orgs/MovingBlocks/projects/26' + github-token: ${{ secrets.PROJECT_GITHUB_TOKEN }} + add_bug_labeled_to_bug_board: + name: Assign bug report issues and pull requests to bug board backlog + if: | + contains(github.event.issue.labels.*.name, 'Type: Bug') || + contains(github.event.pull_request.labels.*.name, 'Type: Bug') + runs-on: ubuntu-latest + steps: + - name: Add to bug board + uses: actions/add-to-project@v0.3.0 + with: + project-url: 'https://github.com/orgs/MovingBlocks/projects/29' + github-token: ${{ secrets.PROJECT_GITHUB_TOKEN }} + add_maintenance_to_stabilization_board: + name: Assign stabilization / refactoring / chore issues and pull requests to stabilization board backlog + if: | + github.event.action != 'opened' && + ( contains(github.event.issue.labels.*.name, 'Type: Refactoring') || + contains(github.event.pull_request.labels.*.name, 'Type: Refactoring') || + contains(github.event.issue.labels.*.name, 'Type: Chore') || + contains(github.event.pull_request.labels.*.name, 'Type: Chore') || + contains(github.event.issue.labels.*.name, 'Topic: Stabilization') && !contains(github.event.issue.labels.*.name, 'Type: Bug') || + contains(github.event.pull_request.labels.*.name, 'Topic: Stabilization') && !contains(github.event.pull_request.labels.*.name, 'Type: Bug') ) + runs-on: ubuntu-latest + steps: + - name: Add to stabilization board + uses: actions/add-to-project@v0.3.0 + with: + project-url: 'https://github.com/orgs/MovingBlocks/projects/25' + github-token: ${{ secrets.PROJECT_GITHUB_TOKEN }} + add_features_to_feature_board: + name: Assign improvement issues and pull requests to feature board backlog + if: | + github.event.action != 'opened' && + contains(github.event.issue.labels.*.name, 'Type: Improvement') && !contains(github.event.issue.labels.*.name, 'Topic: Stabilization') || + contains(github.event.pull_request.labels.*.name, 'Type: Improvement') && !contains(github.event.pull_request.labels.*.name, 'Topic: Stabilization') + runs-on: ubuntu-latest + steps: + - name: Add to feature board + uses: actions/add-to-project@v0.3.0 + with: + project-url: 'https://github.com/orgs/MovingBlocks/projects/27' + github-token: ${{ secrets.PROJECT_GITHUB_TOKEN }} From f95b815040c0e119b7f43c68bfad9466d2e006c4 Mon Sep 17 00:00:00 2001 From: Tobias Nett Date: Sat, 26 Nov 2022 12:59:55 +0100 Subject: [PATCH 009/100] doc: document restrictions for BindButtonEvent (#5079) While working on Terasology/FlexiblePathfinding#8 we noticed that there seem to be issues with sending events extending from BindButtonEvent programmatically (without the physical button press). To avoid any future confusion (or at least find the issue sooner) I'm adding a brief docstring to the class stating it's intended use and that one should NOT use it directly. --- .../org/terasology/engine/input/BindButtonEvent.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/engine/src/main/java/org/terasology/engine/input/BindButtonEvent.java b/engine/src/main/java/org/terasology/engine/input/BindButtonEvent.java index 91ccde6b5dd..564e6c7a137 100644 --- a/engine/src/main/java/org/terasology/engine/input/BindButtonEvent.java +++ b/engine/src/main/java/org/terasology/engine/input/BindButtonEvent.java @@ -1,4 +1,4 @@ -// Copyright 2021 The Terasology Foundation +// Copyright 2022 The Terasology Foundation // SPDX-License-Identifier: Apache-2.0 package org.terasology.engine.input; @@ -7,6 +7,15 @@ import org.terasology.engine.input.events.ButtonEvent; import org.terasology.input.ButtonState; +/** + * An event triggered by auto-registered (physical) buttons when they are pressed. + *

+ * To be used in combination with {@link RegisterBindButton} and {@link DefaultBinding}. + *

+ * Classes extending this usually follow the naming pattern {@code <Name>Button}. + *

+ * NOTE: DO NOT USE DIRECTLY! + */ public class BindButtonEvent extends ButtonEvent { private SimpleUri id; From 119dbecf95ff5e55ed29db0d8608749b5a724fd1 Mon Sep 17 00:00:00 2001 From: Tobias Nett Date: Sat, 17 Dec 2022 21:27:11 +0100 Subject: [PATCH 010/100] fix: remove Rendering Module Settings screen (#5083) We currently don't have a model that allows us to easily load (parts) of modules to make them more configurable in the game setup screens. In particular, the `RenderingModuleRegistry` is not yet initialized, and thus the whole purpose of the rendering module configuration is defied. Therefore, we remove the screen from the menu and also remove the (now dead) code from the repo. Resolves to #5075 Related to #5076 --- .../layers/mainMenu/StartPlayingScreen.java | 25 +-- .../RenderingModuleSettingScreen.java | 209 ------------------ .../ui/menu/renderingModuleSettingScreen.ui | 166 -------------- .../assets/ui/menu/startPlayingScreen.ui | 5 - 4 files changed, 4 insertions(+), 401 deletions(-) delete mode 100644 engine/src/main/java/org/terasology/engine/rendering/nui/layers/mainMenu/videoSettings/RenderingModuleSettingScreen.java delete mode 100644 engine/src/main/resources/org/terasology/engine/assets/ui/menu/renderingModuleSettingScreen.ui diff --git a/engine/src/main/java/org/terasology/engine/rendering/nui/layers/mainMenu/StartPlayingScreen.java b/engine/src/main/java/org/terasology/engine/rendering/nui/layers/mainMenu/StartPlayingScreen.java index b31b6b71a20..ce661fe073d 100644 --- a/engine/src/main/java/org/terasology/engine/rendering/nui/layers/mainMenu/StartPlayingScreen.java +++ b/engine/src/main/java/org/terasology/engine/rendering/nui/layers/mainMenu/StartPlayingScreen.java @@ -1,4 +1,4 @@ -// Copyright 2021 The Terasology Foundation +// Copyright 2022 The Terasology Foundation // SPDX-License-Identifier: Apache-2.0 package org.terasology.engine.rendering.nui.layers.mainMenu; @@ -17,7 +17,6 @@ import org.terasology.engine.rendering.assets.texture.Texture; import org.terasology.engine.rendering.nui.CoreScreenLayer; import org.terasology.engine.rendering.nui.animation.MenuAnimationSystems; -import org.terasology.engine.rendering.nui.layers.mainMenu.videoSettings.RenderingModuleSettingScreen; import org.terasology.engine.rendering.world.WorldSetupWrapper; import org.terasology.engine.world.internal.WorldInfo; import org.terasology.engine.world.time.WorldTime; @@ -49,7 +48,6 @@ public class StartPlayingScreen extends CoreScreenLayer { private List worldSetupWrappers; private UniverseWrapper universeWrapper; private WorldSetupWrapper targetWorld; - private Context subContext; @Override public void initialise() { @@ -91,21 +89,7 @@ public void initialise() { : NetworkMode.NONE)); }); - WidgetUtil.trySubscribe(this, "mainMenu", button -> { - getManager().pushScreen("engine:mainMenuScreen"); - }); - - WidgetUtil.trySubscribe(this, "renderingSettings", button -> { - RenderingModuleSettingScreen renderingModuleSettingScreen = (RenderingModuleSettingScreen) getManager() - .getScreen(RenderingModuleSettingScreen.ASSET_URI); - if (renderingModuleSettingScreen == null) { - renderingModuleSettingScreen = getManager() - .createScreen(RenderingModuleSettingScreen.ASSET_URI, RenderingModuleSettingScreen.class); - renderingModuleSettingScreen.setSubContext(this.subContext); - renderingModuleSettingScreen.postInit(); - } - triggerForwardAnimation(renderingModuleSettingScreen); - }); + WidgetUtil.trySubscribe(this, "mainMenu", button -> getManager().pushScreen("engine:mainMenuScreen")); } @Override @@ -120,8 +104,8 @@ public void onOpened() { } /** - * This method is called before the screen comes to the forefront to set the world - * in which the player is about to spawn. + * This method is called before the screen comes to the forefront to set the world in which the player is about to spawn. + * * @param worldSetupWrapperList The world in which the player is going to spawn. * @param targetWorldTexture The world texture generated in {@link WorldPreGenerationScreen} to be displayed on this screen. */ @@ -131,7 +115,6 @@ public void setTargetWorld(List worldSetupWrapperList, WorldS worldSetupWrappers = worldSetupWrapperList; universeWrapper = context.get(UniverseWrapper.class); targetWorld = spawnWorld; - subContext = context; } @Override diff --git a/engine/src/main/java/org/terasology/engine/rendering/nui/layers/mainMenu/videoSettings/RenderingModuleSettingScreen.java b/engine/src/main/java/org/terasology/engine/rendering/nui/layers/mainMenu/videoSettings/RenderingModuleSettingScreen.java deleted file mode 100644 index 4893d053a26..00000000000 --- a/engine/src/main/java/org/terasology/engine/rendering/nui/layers/mainMenu/videoSettings/RenderingModuleSettingScreen.java +++ /dev/null @@ -1,209 +0,0 @@ -// Copyright 2021 The Terasology Foundation -// SPDX-License-Identifier: Apache-2.0 -package org.terasology.engine.rendering.nui.layers.mainMenu.videoSettings; - -import org.joml.Vector2i; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.terasology.gestalt.assets.ResourceUrn; -import org.terasology.engine.context.Context; -import org.terasology.engine.core.GameEngine; -import org.terasology.engine.core.modes.StateMainMenu; -import org.terasology.engine.core.module.rendering.RenderingModuleRegistry; -import org.terasology.engine.i18n.TranslationSystem; -import org.terasology.gestalt.module.ModuleEnvironment; -import org.terasology.nui.Canvas; -import org.terasology.nui.WidgetUtil; -import org.terasology.nui.databinding.Binding; -import org.terasology.nui.databinding.ReadOnlyBinding; -import org.terasology.nui.itemRendering.StringTextRenderer; -import org.terasology.nui.widgets.UIButton; -import org.terasology.nui.widgets.UIDropdownScrollable; -import org.terasology.nui.widgets.UISlider; -import org.terasology.nui.widgets.UISliderOnChangeTriggeredListener; -import org.terasology.nui.widgets.UIText; -import org.terasology.engine.registry.In; -import org.terasology.engine.rendering.dag.ModuleRendering; -import org.terasology.engine.rendering.nui.CoreScreenLayer; -import org.terasology.engine.rendering.nui.layers.mainMenu.StartPlayingScreen; - -import java.util.ArrayList; -import java.util.List; - -public class RenderingModuleSettingScreen extends CoreScreenLayer implements UISliderOnChangeTriggeredListener { - public static final ResourceUrn ASSET_URI = new ResourceUrn("engine:renderingModuleSettingScreen"); - - private static final Logger logger = LoggerFactory.getLogger(RenderingModuleSettingScreen.class); - - private List orderedModuleRenderingInstances = new ArrayList<>(); - - @In - private Context context; - private Context subContext; - private ModuleEnvironment moduleEnvironment; - - @In - private TranslationSystem translationSystem; - - private RenderingModuleRegistry renderingModuleRegistry; - - private UIText renderingModuleInfo; - private UIButton recalculateOrder; - private UISlider initPrioritySlider; - private UIDropdownScrollable moduleList; - private UIButton setEnabledRenderingClassButton; - - public RenderingModuleSettingScreen() { -//This must be here not in constructor. subContext is set post-creation - - } - - public void postInit() { - moduleEnvironment = subContext.get(ModuleEnvironment.class); - - renderingModuleRegistry = context.get(RenderingModuleRegistry.class); - - orderedModuleRenderingInstances = renderingModuleRegistry.updateRenderingModulesOrder(moduleEnvironment, subContext); - - if (orderedModuleRenderingInstances.isEmpty()) { - logger.error("No rendering module found!"); - GameEngine gameEngine = context.get(GameEngine.class); - gameEngine.changeState(new StateMainMenu("No rendering module installed, unable to render. Try enabling CoreRendering.")); - return; - } - - renderingModuleInfo = find("modulesInfo", UIText.class); - recalculateOrder = find("update", UIButton.class); - setEnabledRenderingClassButton = find("setEnabledRenderingClassButton", UIButton.class); - - // List orderedModuleNames = new ArrayList<>(); - // orderedModuleRenderingInstances.forEach(module->orderedModuleNames.add(module.getProvidingModule())); - - initPrioritySlider = find("moduleInitPrioritySlider", UISlider.class); - if (initPrioritySlider != null) { - initPrioritySlider.setValue(2f); - initPrioritySlider.setUiSliderOnChangeTriggeredListener(this); - } - - moduleList = find("moduleNameList", UIDropdownScrollable.class); - if (moduleList != null) { - moduleList.bindSelection(new Binding() { - ModuleRendering selected; - @Override - public ModuleRendering get() { - return selected; - } - - @Override - public void set(ModuleRendering value) { - if (initPrioritySlider != null) { - initPrioritySlider.setValue(value.getInitPriority()); - } - selected = value; - } - }); - - moduleList.setOptions(orderedModuleRenderingInstances); - moduleList.setVisibleOptions(5); - moduleList.setSelection(orderedModuleRenderingInstances.get(0)); - if (initPrioritySlider != null) { - initPrioritySlider.setValue(moduleList.getSelection().getInitPriority()); - } - moduleList.setOptionRenderer(new StringTextRenderer() { - @Override - public String getString(ModuleRendering value) { - if (value != null) { - StringBuilder stringBuilder = new StringBuilder() - .append(String.format("%s", value.getClass().getSimpleName())); - return stringBuilder.toString(); - } - return ""; - } - @Override - public void draw(ModuleRendering value, Canvas canvas) { - canvas.drawText(getString(value), canvas.getRegion()); - } - }); - } - - if (recalculateOrder != null) { - updateRenderingModuleInfo(); - recalculateOrder.subscribe(button -> { - renderingModuleRegistry.updateRenderingModulesOrder(moduleEnvironment, subContext); - orderedModuleRenderingInstances = renderingModuleRegistry.getOrderedRenderingModules(); - updateRenderingModuleInfo(); - }); - } - - if (setEnabledRenderingClassButton != null && moduleList != null) { - setEnabledRenderingClassButton.bindText(new ReadOnlyBinding() { - @Override - public String get() { - return (moduleList.getSelection().isEnabled()) - ? translationSystem.translate("${engine:menu#disable-rendering-class}") - : translationSystem.translate("${engine:menu#enable-rendering-class}"); - } - }); - - setEnabledRenderingClassButton.subscribe(button -> { - moduleList.getSelection().toggleEnabled(); - updateRenderingModuleInfo(); - }); - } - - - // TODO returns one more screen every time...gradually - WidgetUtil.trySubscribe(this, "return", widget -> - getManager().pushScreen(StartPlayingScreen.ASSET_URI)); - - // Update slider if module selection changes -// if (initPrioritySlider != null && moduleList != null) { -// moduleList. (this, "moduleNameList", widget->initPrioritySlider.setValue(moduleList.getSelection().getInitPriority())); -// } - } - - @Override - public void onOpened() { - super.onOpened(); - - } - - private void updateRenderingModuleInfo() { - StringBuilder infoText = new StringBuilder("Rendering Modules\n").append("=================\n"); - int[] idx = {1}; - if (!orderedModuleRenderingInstances.isEmpty()) { - orderedModuleRenderingInstances.forEach( - (module) -> infoText.append( - String.format("%d. %s - in %s module (Priority: %d, Enabled: %s)\n", - idx[0]++, - module.getClass().getSimpleName(), - module.getProvidingModule(), - module.getInitPriority(), - String.valueOf(module.isEnabled()) - ) - ) - ); - } - renderingModuleInfo.setText(infoText.toString()); - } - - @Override - public void onSliderValueChanged(float val) { - moduleList.getSelection().setInitPriority((int) val); - updateRenderingModuleInfo(); - } - - @Override - public void initialise() { - - } - - public void setSubContext(Context subContext) { - this.subContext = subContext; - } - - @Override - public Vector2i getPreferredContentSize(Canvas canvas, Vector2i vector2i) { - return vector2i; - } -} diff --git a/engine/src/main/resources/org/terasology/engine/assets/ui/menu/renderingModuleSettingScreen.ui b/engine/src/main/resources/org/terasology/engine/assets/ui/menu/renderingModuleSettingScreen.ui deleted file mode 100644 index 963bb0ca444..00000000000 --- a/engine/src/main/resources/org/terasology/engine/assets/ui/menu/renderingModuleSettingScreen.ui +++ /dev/null @@ -1,166 +0,0 @@ -{ - "type": "engine:RenderingModuleSettingScreen", - "skin": "engine:mainMenu", - "contents": { - "fillVerticalSpace": false, - "type": "ColumnLayout", - "columns": 3, - "column-widths": [ - 0.2, - 0.4, - 0.325 - ], - "verticalSpacing": 8, - "horizontalSpacing": 32, - "id": "settingPage", - "contents": [ - { - "type": "engine:RelativeLayout", - "id": "newWidget", - "contents": [ - { - "type": "UIButton", - "text": "${engine:menu#return}", - "id": "return", - "layoutInfo": { - "position-vertical-center": {}, - "position-left": { - "target": "LEFT", - "offset": 300 - }, - "position-right": { - "target": "LEFT", - "widget": "configContainer" - }, - "width": 120, - "height": 80 - } - } - ] - }, - { - "type": "relativeLayout", - "id": "configContainer", - "layoutInfo": { - "position-horizontal-center": {}, - "position-right": { - "target": "RIGHT" - }, - "position-vertical-center": {} - }, - "contents": [ - { - "type": "UILabel", - "id": "title", - "family": "title", - "text": "${engine:menu#rendering-settings}", - "layoutInfo": { - "height": 48, - "position-horizontal-center": {}, - "position-bottom": { - "target": "TOP", - "widget": "modulesInfo", - "offset": 64 - } - } - }, - { - "type": "UIText", - "text": "empty", - "id": "modulesInfo", - "layoutInfo": { - "position-vertical-center": {}, - "height": 300 - }, - "multiline": true, - "readOnly": true - }, - { - "type": "UIButton", - "text": "${engine:menu#update-module}", - "id": "update", - "layoutInfo": { - "width": 120, - "height": 80, - "position-horizontal-center": {}, - "position-top": { - "widget": "modulesInfo", - "target": "BOTTOM", - "offset": 16 - } - } - } - ] - }, - { - "type": "ColumnLayout", - "id": "moduleSelectionLayout", - "contents": [ - { - "type": "UIDropdownScrollable", - "id": "moduleNameList", - "layoutInfo": { - "position-horizontal-center": {}, - "position-vertical-center": {}, - "position-left": { - "target": "RIGHT", - "widget": "modulesInfo" - } - }, - "relativeWidth": 0.7 - }, - { - "type": "UISlider", - "id": "moduleInitPrioritySlider", - "increment": 1.0, - "value": 1.0, - "precision": 0, - "range": 100.0, - "minimum": 1.0, - "layoutInfo": { - "position-top": { - "target": "BOTTOM", - "widget": "moduleNameList", - "offset": 48 - }, - "relativeWidth": 0.5 - } - }, - { - "type": "UIButton", - "text": "${engine:menu#disable-rendering-class}", - "id": "setEnabledRenderingClassButton", - "layoutInfo": { - "position-horizontal-center": {}, - "width": 80, - "height": 40, - "position-top": { - "target": "BOTTOM", - "widget": "moduleInitPrioritySlider", - "offset": 8 - } - } - } - ], - "layoutInfo": { - "position-vertical-center": {}, - "position-left": { - "target": "RIGHT", - "widget": "modulesInfo", - "offset": 16 - } - }, - "verticalSpacing": 16 - } - ], - "autoSizeColumns": false, - "layoutInfo": { - "position-vertical-center": {}, - "position-horizontal-center": {}, - "position-left": { - "target": "LEFT", - "offset": 48 - } - } - } -} \ No newline at end of file diff --git a/engine/src/main/resources/org/terasology/engine/assets/ui/menu/startPlayingScreen.ui b/engine/src/main/resources/org/terasology/engine/assets/ui/menu/startPlayingScreen.ui index 6bf6c9305d5..86d0f44435c 100644 --- a/engine/src/main/resources/org/terasology/engine/assets/ui/menu/startPlayingScreen.ui +++ b/engine/src/main/resources/org/terasology/engine/assets/ui/menu/startPlayingScreen.ui @@ -97,11 +97,6 @@ "text": "${engine:menu#return-main-menu}", "id": "mainMenu" }, - { - "type": "UIButton", - "text": "${engine:menu#rendering-settings}", - "id": "renderingSettings" - }, { "type": "UIButton", "text": "${engine:menu#start-playing}", From 2666812b8c53cf1ac2aa9ba5c59d79c1ea5a3413 Mon Sep 17 00:00:00 2001 From: Liquid-Cat <38368405+MrGizmo123@users.noreply.github.com> Date: Sun, 18 Dec 2022 02:48:16 +0530 Subject: [PATCH 011/100] feat: add check for facet provider requirements (#5073) * add check for required facets in WorldBuilder * throw IllegalStateException on missing provider for required facet * adjust test cases for facet provider order * add test case for incorrect order throwing * remove .idea/kotlinc.xml Fixes #4924 Co-authored-by: jdrueckert --- .idea/kotlinc.xml | 6 ------ .../world/generation/WorldBuilderTest.java | 20 +++++++++++++++---- .../engine/world/generation/WorldBuilder.java | 12 +++++++++++ 3 files changed, 28 insertions(+), 10 deletions(-) delete mode 100644 .idea/kotlinc.xml diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml deleted file mode 100644 index 7e340a776a6..00000000000 --- a/.idea/kotlinc.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/engine-tests/src/test/java/org/terasology/engine/world/generation/WorldBuilderTest.java b/engine-tests/src/test/java/org/terasology/engine/world/generation/WorldBuilderTest.java index 5bbb1a8a774..a631ac7b49e 100644 --- a/engine-tests/src/test/java/org/terasology/engine/world/generation/WorldBuilderTest.java +++ b/engine-tests/src/test/java/org/terasology/engine/world/generation/WorldBuilderTest.java @@ -11,6 +11,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; public class WorldBuilderTest { @@ -21,8 +22,8 @@ public class WorldBuilderTest { public void testBorderCalculation() { WorldBuilder worldBuilder = new WorldBuilder(context.get(WorldGeneratorPluginLibrary.class)); worldBuilder.setSeed(12); - worldBuilder.addProvider(new Facet1Provider()); worldBuilder.addProvider(new Facet2Provider()); + worldBuilder.addProvider(new Facet1Provider()); World world = worldBuilder.build(); BlockRegion regionToGenerate = new BlockRegion(0, 0, 0).expand(1, 1, 1); @@ -39,8 +40,8 @@ public void testBorderCalculation() { public void testCumulativeBorderCalculation() { WorldBuilder worldBuilder = new WorldBuilder(context.get(WorldGeneratorPluginLibrary.class)); worldBuilder.setSeed(12); - worldBuilder.addProvider(new Facet1Provider()); worldBuilder.addProvider(new Facet2Provider()); + worldBuilder.addProvider(new Facet1Provider()); worldBuilder.addProvider(new Facet3Provider()); World world = worldBuilder.build(); @@ -61,8 +62,8 @@ public void testCumulativeBorderCalculation() { public void testMultiplePathsBorderCalculation() { WorldBuilder worldBuilder = new WorldBuilder(context.get(WorldGeneratorPluginLibrary.class)); worldBuilder.setSeed(12); - worldBuilder.addProvider(new Facet1Provider()); worldBuilder.addProvider(new Facet2Provider()); + worldBuilder.addProvider(new Facet1Provider()); worldBuilder.addProvider(new Facet4Provider()); World world = worldBuilder.build(); @@ -84,8 +85,8 @@ public void testMultiplePathsBorderCalculation() { public void testUpdating() { WorldBuilder worldBuilder = new WorldBuilder(context.get(WorldGeneratorPluginLibrary.class)); worldBuilder.setSeed(12); - worldBuilder.addProvider(new Facet1Provider()); worldBuilder.addProvider(new Facet2Provider()); + worldBuilder.addProvider(new Facet1Provider()); worldBuilder.addProvider(new Facet3Provider()); worldBuilder.addProvider(new Facet4Provider()); worldBuilder.addProvider(new FacetUpdater()); @@ -108,6 +109,17 @@ public void testUpdating() { assertTrue(regionData.getFacet(Facet4.class).updated); } + @Test + public void testIncorrectProviderOrder() { + WorldBuilder worldBuilder = new WorldBuilder(context.get(WorldGeneratorPluginLibrary.class)); + worldBuilder.setSeed(12); + // Facet1Provider requires Facet2Provider, add them in incorrect order + worldBuilder.addProvider(new Facet1Provider()); + worldBuilder.addProvider(new Facet2Provider()); + + assertThrows(IllegalStateException.class, worldBuilder::build); + } + public static class Facet1 extends BaseFacet3D { public boolean updated; diff --git a/engine/src/main/java/org/terasology/engine/world/generation/WorldBuilder.java b/engine/src/main/java/org/terasology/engine/world/generation/WorldBuilder.java index 90a5ac0fe84..120f070875f 100644 --- a/engine/src/main/java/org/terasology/engine/world/generation/WorldBuilder.java +++ b/engine/src/main/java/org/terasology/engine/world/generation/WorldBuilder.java @@ -179,6 +179,17 @@ private ListMultimap, FacetProvider> determineProvid if (produces != null) { facets.addAll(Arrays.asList(produces.value())); } + + Requires requires = provider.getClass().getAnnotation(Requires.class); + if (requires != null) { + for (Facet facet : requires.value()) { + if (!facets.contains(facet.value())) { + logger.error("Facet provider for {} is missing. It is required by {}", facet.value(), provider.getClass()); + throw new IllegalStateException("Missing facet provider"); + } + } + } + Updates updates = provider.getClass().getAnnotation(Updates.class); if (updates != null) { for (Facet facet : updates.value()) { @@ -186,6 +197,7 @@ private ListMultimap, FacetProvider> determineProvid } } } + for (Class facet : facets) { if (!result.containsKey(facet)) { Set orderedProviders = Sets.newLinkedHashSet(); From ecf17d9874b2e9c4d4078846f74b3a3a6728ccdd Mon Sep 17 00:00:00 2001 From: Kevin Turner <83819+keturn@users.noreply.github.com> Date: Sun, 18 Dec 2022 04:35:05 -0800 Subject: [PATCH 012/100] test(MTE): new parametrizable IntegrationEnvironment annotation for MTE tests (#5046) * make `@IntegrationEnvironment` automatically apply MTEExtension and tag "MteTest" * replace `@UseWorldGenerator` with IntegrationEnvironment(worldGenerator) * replace `@Dependencies` with IntegrationEnvironment(dependencies) * if no dependencies are given, default to the module defined in the current directory * deprecate `@UseWorldGenerator` annotation * deprecate `@Dependencies` annotation Fixes Terasology/ModuleTestingEnvironment#76 --- .../integrationenvironment/Engines.java | 6 ++-- .../TestingStateHeadlessSetup.java | 20 +++++++++-- .../jupiter/Dependencies.java | 3 +- .../jupiter/IntegrationEnvironment.java | 21 ++++++++++++ .../jupiter/MTEExtension.java | 32 +++++++---------- .../jupiter/UseWorldGenerator.java | 3 +- .../AssetLoadingTest.java | 7 ++-- .../ChunkRegionFutureTest.java | 5 --- .../ClientConnectionTest.java | 5 --- .../ComponentSystemTest.java | 7 ++-- .../CustomSubsystemTest.java | 5 --- .../integrationenvironment/ExampleTest.java | 5 --- .../LifecyclePerClassInjectionTest.java | 7 ++-- .../LifecyclePerMethodInjectionTest.java | 7 ++-- ...MTEExtensionTestWithPerClassLifecycle.java | 7 ++-- ...TEExtensionTestWithPerMethodLifecycle.java | 7 ++-- .../ModuleTestingEnvironmentTest.java | 7 ++-- .../integrationenvironment/NestedTest.java | 7 ++-- .../NetworkModeLocalServerTest.java | 5 --- .../NetworkModeNoneTest.java | 5 --- .../NetworkModeServerTest.java | 5 --- .../SubclassInjectionTest.java | 7 ++-- .../TestEventReceiverTest.java | 7 ++-- .../WorldProviderTest.java | 7 ++-- .../delay/DelayManagerTest.java | 5 --- .../engine/core/module/ModuleManager.java | 34 ++++++++++++++++++- 26 files changed, 114 insertions(+), 122 deletions(-) diff --git a/engine-tests/src/main/java/org/terasology/engine/integrationenvironment/Engines.java b/engine-tests/src/main/java/org/terasology/engine/integrationenvironment/Engines.java index ea653a0c93d..b061cb7e6a9 100644 --- a/engine-tests/src/main/java/org/terasology/engine/integrationenvironment/Engines.java +++ b/engine-tests/src/main/java/org/terasology/engine/integrationenvironment/Engines.java @@ -67,10 +67,12 @@ * host. Currently all engine instances are headless, though it is possible to use headed engines in the future. */ public class Engines { + public static final String DEFAULT_WORLD_GENERATOR = ModuleTestingEnvironment.DEFAULT_WORLD_GENERATOR; + private static final Logger logger = LoggerFactory.getLogger(Engines.class); - protected final Set dependencies = Sets.newHashSet("engine"); - protected String worldGeneratorUri = ModuleTestingEnvironment.DEFAULT_WORLD_GENERATOR; + protected final Set dependencies = Sets.newHashSet(); + protected String worldGeneratorUri = DEFAULT_WORLD_GENERATOR; protected boolean doneLoading; protected Context hostContext; protected final List engines = Lists.newArrayList(); diff --git a/engine-tests/src/main/java/org/terasology/engine/integrationenvironment/TestingStateHeadlessSetup.java b/engine-tests/src/main/java/org/terasology/engine/integrationenvironment/TestingStateHeadlessSetup.java index 889a5fc53eb..8594da63157 100644 --- a/engine-tests/src/main/java/org/terasology/engine/integrationenvironment/TestingStateHeadlessSetup.java +++ b/engine-tests/src/main/java/org/terasology/engine/integrationenvironment/TestingStateHeadlessSetup.java @@ -2,6 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 package org.terasology.engine.integrationenvironment; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.terasology.engine.config.Config; import org.terasology.engine.config.ModuleConfig; import org.terasology.engine.config.WorldGenerationConfig; @@ -9,12 +11,14 @@ import org.terasology.engine.core.SimpleUri; import org.terasology.engine.core.TerasologyConstants; import org.terasology.engine.core.TerasologyEngine; +import org.terasology.engine.core.module.ModuleManager; import org.terasology.engine.core.subsystem.headless.mode.StateHeadlessSetup; import org.terasology.engine.game.GameManifest; import org.terasology.engine.network.NetworkMode; import org.terasology.engine.world.time.WorldTime; import org.terasology.gestalt.naming.Name; +import java.nio.file.Path; import java.util.Collection; import java.util.Set; import java.util.stream.Collectors; @@ -27,6 +31,8 @@ public class TestingStateHeadlessSetup extends StateHeadlessSetup { static final String WORLD_TITLE = "testworld"; static final String DEFAULT_SEED = "seed"; + private static final Logger logger = LoggerFactory.getLogger(TestingStateHeadlessSetup.class); + private final Collection dependencies; private final SimpleUri worldGeneratorUri; @@ -41,9 +47,18 @@ public TestingStateHeadlessSetup(Collection dependencies, String worldGe checkArgument(this.worldGeneratorUri.isValid(), "Not a valid URI `%s`", worldGeneratorUri); } - void configForTest(Config config) { + void configForTest(Config config, ModuleManager moduleManager) { Set dependencyNames = dependencies.stream().map(Name::new).collect(Collectors.toSet()); + if (dependencyNames.isEmpty()) { + Path workingDirectory = Path.of(".").toAbsolutePath().normalize(); + var defaultModule = moduleManager.getModuleAt(workingDirectory); + logger.info( + "No module dependencies given. Checked current directory `{}`, found `{}` to use as default.", + workingDirectory, defaultModule.orElse(null)); + defaultModule.ifPresent(m -> dependencyNames.add(m.getId())); + } + // Include the MTE module to provide world generators and suchlike. dependencyNames.add(MTE_MODULE_NAME); @@ -70,7 +85,8 @@ public GameManifest createGameManifest() { public void init(GameEngine engine) { // We want to modify Config before super.init calls createGameManifest, but the child context // does not exist before we call super.init. - configForTest(((TerasologyEngine) engine).getFromEngineContext(Config.class)); + var tEngine = (TerasologyEngine) engine; + configForTest(tEngine.getFromEngineContext(Config.class), tEngine.getFromEngineContext(ModuleManager.class)); super.init(engine); } } diff --git a/engine-tests/src/main/java/org/terasology/engine/integrationenvironment/jupiter/Dependencies.java b/engine-tests/src/main/java/org/terasology/engine/integrationenvironment/jupiter/Dependencies.java index 4aa5da9b628..cdf2b88353e 100644 --- a/engine-tests/src/main/java/org/terasology/engine/integrationenvironment/jupiter/Dependencies.java +++ b/engine-tests/src/main/java/org/terasology/engine/integrationenvironment/jupiter/Dependencies.java @@ -11,10 +11,11 @@ /** * Declares the modules to load in the environment. * - * @see MTEExtension + * @deprecated Replace with {@link IntegrationEnvironment#dependencies} */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) +@Deprecated(since = "5.3.0", forRemoval = true) public @interface Dependencies { /** * Names of modules, as defined by the id in their module.txt. diff --git a/engine-tests/src/main/java/org/terasology/engine/integrationenvironment/jupiter/IntegrationEnvironment.java b/engine-tests/src/main/java/org/terasology/engine/integrationenvironment/jupiter/IntegrationEnvironment.java index 2a13b6fd677..1e9775f65ea 100644 --- a/engine-tests/src/main/java/org/terasology/engine/integrationenvironment/jupiter/IntegrationEnvironment.java +++ b/engine-tests/src/main/java/org/terasology/engine/integrationenvironment/jupiter/IntegrationEnvironment.java @@ -3,10 +3,14 @@ package org.terasology.engine.integrationenvironment.jupiter; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.extension.ExtendWith; import org.terasology.engine.core.subsystem.EngineSubsystem; +import org.terasology.engine.integrationenvironment.Engines; import org.terasology.engine.logic.players.LocalPlayer; import org.terasology.engine.network.NetworkMode; +import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -14,7 +18,17 @@ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) +@Documented +@Tag("MteTest") +@ExtendWith(MTEExtension.class) public @interface IntegrationEnvironment { + /** + * Modules to include in the environment. + *

+ * Names of modules, as defined by the {@code id} in their {@code module.txt}. + */ + String[] dependencies() default { }; + /** * The network mode the host engine starts with. *

@@ -38,6 +52,13 @@ */ Class subsystem() default NO_SUBSYSTEM.class; + /** + * The URN of the world generator. + *

+ * For example, {@code "CoreWorlds:facetedSimplex"} + */ + String worldGenerator() default Engines.DEFAULT_WORLD_GENERATOR; + /** * Do not add an extra subsystem to the integration environment. *

diff --git a/engine-tests/src/main/java/org/terasology/engine/integrationenvironment/jupiter/MTEExtension.java b/engine-tests/src/main/java/org/terasology/engine/integrationenvironment/jupiter/MTEExtension.java index 8e7f9cd1b68..96b7ddc90c7 100644 --- a/engine-tests/src/main/java/org/terasology/engine/integrationenvironment/jupiter/MTEExtension.java +++ b/engine-tests/src/main/java/org/terasology/engine/integrationenvironment/jupiter/MTEExtension.java @@ -19,7 +19,6 @@ import org.terasology.engine.integrationenvironment.ModuleTestingHelper; import org.terasology.engine.network.NetworkMode; import org.terasology.engine.registry.In; -import org.terasology.unittest.worlds.DummyWorldGenerator; import java.util.Arrays; import java.util.Collections; @@ -37,13 +36,10 @@ *

* Example: *


- * import org.junit.jupiter.api.extension.ExtendWith;
  * import org.junit.jupiter.api.Test;
  * import org.terasology.engine.registry.In;
  *
- * @{@link org.junit.jupiter.api.extension.ExtendWith ExtendWith}(MTEExtension.class)
- * @{@link Dependencies}("MyModule")
- * @{@link UseWorldGenerator}("Pathfinding:pathfinder")
+ * @{@link IntegrationEnvironment}(worldGenerator="Pathfinding:pathfinder")
  * public class ExampleTest {
  *
  *     @In
@@ -65,18 +61,8 @@
  * }
  * 
*

- * You can configure the environment with these additional annotations: - *

- *
{@link IntegrationEnvironment @IntegrationEnvironment}
- *
Configure the network mode and add subsystems.
- *
{@link Dependencies @Dependencies}
- *
Specify which modules to include in the environment. Put the name of your module under test here. - * Any dependencies these modules declare in module.txt will be pulled in as well.
- *
{@link UseWorldGenerator @UseWorldGenerator}
- *
The URN of the world generator to use. Defaults to {@link DummyWorldGenerator}, - * a flat world.
- *
- * + * See {@link IntegrationEnvironment @IntegrationEnvironment} for information on how to configure the + * environment's dependencies, world type, and subsystems. *

* By default, JUnit uses a {@link org.junit.jupiter.api.TestInstance.Lifecycle#PER_METHOD PER_METHOD} lifecycle * for test instances. The instance of your test class—i.e. {@code this} when your test method executes— @@ -163,14 +149,20 @@ private Object getDIInstance(Engines engines, Class type) { } } + @SuppressWarnings("removal") // TODO: replace UseWorldGenerator in modules public String getWorldGeneratorUri(ExtensionContext context) { return findAnnotation(context.getRequiredTestClass(), UseWorldGenerator.class) - .map(UseWorldGenerator::value).orElse(null); + .map(UseWorldGenerator::value) + .orElseGet(() -> getAnnotationWithDefault(context, IntegrationEnvironment::worldGenerator)); } + @SuppressWarnings("removal") // TODO: replace or remove Dependencies in modules public List getDependencyNames(ExtensionContext context) { return findAnnotation(context.getRequiredTestClass(), Dependencies.class) - .map(a -> Arrays.asList(a.value())).orElse(Collections.emptyList()); + .map(a -> Arrays.asList(a.value())) + .orElseGet(() -> Arrays.asList( + getAnnotationWithDefault(context, IntegrationEnvironment::dependencies) + )); } public NetworkMode getNetworkMode(ExtensionContext context) { @@ -193,7 +185,7 @@ private T getAnnotationWithDefault(ExtensionContext context, Function - * The new Engines instance is configured using the {@link Dependencies} and {@link UseWorldGenerator} + * The new Engines instance is configured using the {@link IntegrationEnvironment} * annotations for the test class. *

* This will create a new instance when necessary. It will be stored in the diff --git a/engine-tests/src/main/java/org/terasology/engine/integrationenvironment/jupiter/UseWorldGenerator.java b/engine-tests/src/main/java/org/terasology/engine/integrationenvironment/jupiter/UseWorldGenerator.java index 75bbd1217b6..28cfd8c1581 100644 --- a/engine-tests/src/main/java/org/terasology/engine/integrationenvironment/jupiter/UseWorldGenerator.java +++ b/engine-tests/src/main/java/org/terasology/engine/integrationenvironment/jupiter/UseWorldGenerator.java @@ -11,10 +11,11 @@ /** * Declares which {@index "world generator"} to use. * - * @see MTEExtension + * @deprecated Replace with {@link IntegrationEnvironment#worldGenerator} */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) +@Deprecated(since = "5.3.0", forRemoval = true) public @interface UseWorldGenerator { /** diff --git a/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/AssetLoadingTest.java b/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/AssetLoadingTest.java index 4462ca8610b..cdbe5ef6200 100644 --- a/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/AssetLoadingTest.java +++ b/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/AssetLoadingTest.java @@ -2,11 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 package org.terasology.engine.integrationenvironment; -import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.terasology.engine.entitySystem.prefab.Prefab; -import org.terasology.engine.integrationenvironment.jupiter.MTEExtension; +import org.terasology.engine.integrationenvironment.jupiter.IntegrationEnvironment; import org.terasology.engine.registry.In; import org.terasology.engine.world.block.Block; import org.terasology.engine.world.block.BlockManager; @@ -16,8 +14,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.terasology.engine.testUtil.Assertions.assertNotEmpty; -@Tag("MteTest") -@ExtendWith(MTEExtension.class) +@IntegrationEnvironment public class AssetLoadingTest { @In diff --git a/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/ChunkRegionFutureTest.java b/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/ChunkRegionFutureTest.java index 4feaf5025e7..4ffa68c4454 100644 --- a/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/ChunkRegionFutureTest.java +++ b/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/ChunkRegionFutureTest.java @@ -7,12 +7,9 @@ import org.joml.Vector3fc; import org.joml.Vector3i; import org.joml.Vector3ic; -import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.terasology.engine.entitySystem.entity.EntityManager; import org.terasology.engine.integrationenvironment.jupiter.IntegrationEnvironment; -import org.terasology.engine.integrationenvironment.jupiter.MTEExtension; import org.terasology.engine.network.NetworkMode; import org.terasology.engine.registry.In; import org.terasology.engine.world.WorldProvider; @@ -25,8 +22,6 @@ import static org.terasology.engine.world.block.BlockManager.AIR_ID; import static org.terasology.engine.world.block.BlockManager.UNLOADED_ID; -@Tag("MteTest") -@ExtendWith(MTEExtension.class) @IntegrationEnvironment(networkMode = NetworkMode.LISTEN_SERVER) class ChunkRegionFutureTest { diff --git a/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/ClientConnectionTest.java b/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/ClientConnectionTest.java index 21e8b55fe4b..ae3d17d74d1 100644 --- a/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/ClientConnectionTest.java +++ b/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/ClientConnectionTest.java @@ -3,23 +3,18 @@ package org.terasology.engine.integrationenvironment; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.terasology.engine.context.Context; import org.terasology.engine.core.TerasologyEngine; import org.terasology.engine.core.modes.StateIngame; import org.terasology.engine.integrationenvironment.jupiter.IntegrationEnvironment; -import org.terasology.engine.integrationenvironment.jupiter.MTEExtension; import org.terasology.engine.network.NetworkMode; import java.io.IOException; import java.util.List; -@Tag("MteTest") -@ExtendWith(MTEExtension.class) @IntegrationEnvironment(networkMode = NetworkMode.LISTEN_SERVER) public class ClientConnectionTest { private static final Logger logger = LoggerFactory.getLogger(ClientConnectionTest.class); diff --git a/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/ComponentSystemTest.java b/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/ComponentSystemTest.java index 2c47ab90222..7d67c60ed62 100644 --- a/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/ComponentSystemTest.java +++ b/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/ComponentSystemTest.java @@ -3,18 +3,15 @@ package org.terasology.engine.integrationenvironment; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.terasology.engine.entitySystem.entity.EntityManager; import org.terasology.engine.entitySystem.entity.EntityRef; -import org.terasology.engine.integrationenvironment.jupiter.MTEExtension; +import org.terasology.engine.integrationenvironment.jupiter.IntegrationEnvironment; import org.terasology.engine.registry.In; import org.terasology.unittest.stubs.DummyComponent; import org.terasology.unittest.stubs.DummyEvent; -@Tag("MteTest") -@ExtendWith(MTEExtension.class) +@IntegrationEnvironment public class ComponentSystemTest { @In private EntityManager entityManager; diff --git a/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/CustomSubsystemTest.java b/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/CustomSubsystemTest.java index 471614765d4..a3b94f81642 100644 --- a/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/CustomSubsystemTest.java +++ b/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/CustomSubsystemTest.java @@ -3,22 +3,17 @@ package org.terasology.engine.integrationenvironment; -import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.terasology.engine.config.PlayerConfig; import org.terasology.engine.context.Context; import org.terasology.engine.core.GameEngine; import org.terasology.engine.core.TerasologyEngine; import org.terasology.engine.core.subsystem.NonPlayerVisibleSubsystem; import org.terasology.engine.integrationenvironment.jupiter.IntegrationEnvironment; -import org.terasology.engine.integrationenvironment.jupiter.MTEExtension; import static com.google.common.truth.Truth.assertThat; import static org.terasology.engine.testUtil.Correspondences.instanceOfExpected; -@Tag("MteTest") -@ExtendWith(MTEExtension.class) @IntegrationEnvironment(subsystem = CustomSubsystemTest.MySubsystem.class) public class CustomSubsystemTest { diff --git a/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/ExampleTest.java b/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/ExampleTest.java index 2b4c44c793e..a7a47840901 100644 --- a/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/ExampleTest.java +++ b/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/ExampleTest.java @@ -5,14 +5,11 @@ import com.google.common.collect.Lists; import org.joml.Vector3i; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.terasology.engine.context.Context; import org.terasology.engine.core.Time; import org.terasology.engine.entitySystem.entity.EntityManager; import org.terasology.engine.integrationenvironment.jupiter.IntegrationEnvironment; -import org.terasology.engine.integrationenvironment.jupiter.MTEExtension; import org.terasology.engine.logic.players.LocalPlayer; import org.terasology.engine.logic.players.event.ResetCameraEvent; import org.terasology.engine.network.ClientComponent; @@ -23,8 +20,6 @@ import java.io.IOException; -@Tag("MteTest") -@ExtendWith(MTEExtension.class) @IntegrationEnvironment(networkMode = NetworkMode.LISTEN_SERVER) public class ExampleTest { diff --git a/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/LifecyclePerClassInjectionTest.java b/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/LifecyclePerClassInjectionTest.java index 8d051ed9aea..a17246273c3 100644 --- a/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/LifecyclePerClassInjectionTest.java +++ b/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/LifecyclePerClassInjectionTest.java @@ -7,22 +7,19 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestMethodOrder; -import org.junit.jupiter.api.extension.ExtendWith; import org.terasology.engine.context.Context; import org.terasology.engine.core.GameEngine; -import org.terasology.engine.integrationenvironment.jupiter.MTEExtension; +import org.terasology.engine.integrationenvironment.jupiter.IntegrationEnvironment; import org.terasology.engine.registry.In; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth8.assertThat; -@Tag("MteTest") -@ExtendWith(MTEExtension.class) +@IntegrationEnvironment @TestMethodOrder(MethodOrderer.OrderAnnotation.class) @TestInstance(TestInstance.Lifecycle.PER_CLASS) public class LifecyclePerClassInjectionTest { diff --git a/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/LifecyclePerMethodInjectionTest.java b/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/LifecyclePerMethodInjectionTest.java index 355ad2adb51..be7d059ff0e 100644 --- a/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/LifecyclePerMethodInjectionTest.java +++ b/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/LifecyclePerMethodInjectionTest.java @@ -5,20 +5,17 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.api.extension.ExtendWith; import org.terasology.engine.context.Context; import org.terasology.engine.core.GameEngine; -import org.terasology.engine.integrationenvironment.jupiter.MTEExtension; +import org.terasology.engine.integrationenvironment.jupiter.IntegrationEnvironment; import org.terasology.engine.registry.In; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth8.assertThat; -@Tag("MteTest") -@ExtendWith(MTEExtension.class) +@IntegrationEnvironment @TestInstance(TestInstance.Lifecycle.PER_METHOD) public class LifecyclePerMethodInjectionTest { diff --git a/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/MTEExtensionTestWithPerClassLifecycle.java b/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/MTEExtensionTestWithPerClassLifecycle.java index 6c4b8cc0fc5..5f3490aeec1 100644 --- a/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/MTEExtensionTestWithPerClassLifecycle.java +++ b/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/MTEExtensionTestWithPerClassLifecycle.java @@ -7,15 +7,13 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestMethodOrder; -import org.junit.jupiter.api.extension.ExtendWith; import org.terasology.engine.entitySystem.entity.EntityManager; import org.terasology.engine.entitySystem.entity.EntityRef; -import org.terasology.engine.integrationenvironment.jupiter.MTEExtension; +import org.terasology.engine.integrationenvironment.jupiter.IntegrationEnvironment; import org.terasology.engine.registry.In; import org.terasology.unittest.stubs.DummyComponent; import org.terasology.unittest.stubs.DummyEvent; @@ -33,8 +31,7 @@ /** * Ensure a test class with a per-class Jupiter lifecycle can share an engine between tests. */ -@Tag("MteTest") -@ExtendWith(MTEExtension.class) +@IntegrationEnvironment @TestInstance(TestInstance.Lifecycle.PER_CLASS) // Lifecycle of the test instance @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class MTEExtensionTestWithPerClassLifecycle { diff --git a/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/MTEExtensionTestWithPerMethodLifecycle.java b/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/MTEExtensionTestWithPerMethodLifecycle.java index 1cf6d110ad8..7cdfa406a83 100644 --- a/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/MTEExtensionTestWithPerMethodLifecycle.java +++ b/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/MTEExtensionTestWithPerMethodLifecycle.java @@ -7,15 +7,13 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestMethodOrder; -import org.junit.jupiter.api.extension.ExtendWith; import org.terasology.engine.entitySystem.entity.EntityManager; import org.terasology.engine.entitySystem.entity.EntityRef; -import org.terasology.engine.integrationenvironment.jupiter.MTEExtension; +import org.terasology.engine.integrationenvironment.jupiter.IntegrationEnvironment; import org.terasology.engine.registry.In; import org.terasology.unittest.stubs.DummyComponent; import org.terasology.unittest.stubs.DummyEvent; @@ -30,10 +28,9 @@ /** * Ensure a test class with a per-method Jupiter lifecycle does not share data between tests. */ -@Tag("MteTest") -@ExtendWith(MTEExtension.class) @TestInstance(TestInstance.Lifecycle.PER_METHOD) // The default, but here for explicitness. @TestMethodOrder(MethodOrderer.OrderAnnotation.class) +@IntegrationEnvironment public class MTEExtensionTestWithPerMethodLifecycle { @In diff --git a/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/ModuleTestingEnvironmentTest.java b/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/ModuleTestingEnvironmentTest.java index cfbd7a7b319..07a63c7c26b 100644 --- a/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/ModuleTestingEnvironmentTest.java +++ b/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/ModuleTestingEnvironmentTest.java @@ -7,16 +7,13 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import com.google.common.util.concurrent.UncheckedTimeoutException; -import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.terasology.engine.integrationenvironment.jupiter.MTEExtension; +import org.terasology.engine.integrationenvironment.jupiter.IntegrationEnvironment; import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; -@Tag("MteTest") -@ExtendWith(MTEExtension.class) +@IntegrationEnvironment public class ModuleTestingEnvironmentTest { public static final int THE_ANSWER = 42; diff --git a/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/NestedTest.java b/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/NestedTest.java index b40e68a03b4..277311cfccf 100644 --- a/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/NestedTest.java +++ b/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/NestedTest.java @@ -4,11 +4,9 @@ package org.terasology.engine.integrationenvironment; import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.terasology.engine.entitySystem.entity.EntityManager; -import org.terasology.engine.integrationenvironment.jupiter.MTEExtension; +import org.terasology.engine.integrationenvironment.jupiter.IntegrationEnvironment; import org.terasology.engine.registry.In; import static com.google.common.truth.Truth.assertThat; @@ -27,8 +25,7 @@ * @see JUnit User Guide: Nested Tests */ -@Tag("MteTest") -@ExtendWith(MTEExtension.class) +@IntegrationEnvironment public class NestedTest { @In public Engines outerEngines; diff --git a/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/NetworkModeLocalServerTest.java b/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/NetworkModeLocalServerTest.java index 83119bb7dc0..786a8910202 100644 --- a/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/NetworkModeLocalServerTest.java +++ b/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/NetworkModeLocalServerTest.java @@ -2,19 +2,14 @@ // SPDX-License-Identifier: Apache-2.0 package org.terasology.engine.integrationenvironment; -import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.terasology.engine.integrationenvironment.jupiter.IntegrationEnvironment; -import org.terasology.engine.integrationenvironment.jupiter.MTEExtension; import org.terasology.engine.logic.players.LocalPlayer; import org.terasology.engine.network.NetworkMode; import org.terasology.engine.network.NetworkSystem; import static com.google.common.truth.Truth.assertThat; -@Tag("MteTest") -@ExtendWith(MTEExtension.class) @IntegrationEnvironment(networkMode = NetworkMode.DEDICATED_SERVER) public class NetworkModeLocalServerTest { @Test diff --git a/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/NetworkModeNoneTest.java b/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/NetworkModeNoneTest.java index 784935e07bc..5f70cf3ef09 100644 --- a/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/NetworkModeNoneTest.java +++ b/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/NetworkModeNoneTest.java @@ -2,19 +2,14 @@ // SPDX-License-Identifier: Apache-2.0 package org.terasology.engine.integrationenvironment; -import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.terasology.engine.integrationenvironment.jupiter.IntegrationEnvironment; -import org.terasology.engine.integrationenvironment.jupiter.MTEExtension; import org.terasology.engine.logic.players.LocalPlayer; import org.terasology.engine.network.NetworkMode; import org.terasology.engine.network.NetworkSystem; import static com.google.common.truth.Truth.assertThat; -@Tag("MteTest") -@ExtendWith(MTEExtension.class) @IntegrationEnvironment(networkMode = NetworkMode.NONE) public class NetworkModeNoneTest { @Test diff --git a/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/NetworkModeServerTest.java b/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/NetworkModeServerTest.java index 0e54205b172..81f6bdcc952 100644 --- a/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/NetworkModeServerTest.java +++ b/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/NetworkModeServerTest.java @@ -2,19 +2,14 @@ // SPDX-License-Identifier: Apache-2.0 package org.terasology.engine.integrationenvironment; -import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.terasology.engine.integrationenvironment.jupiter.IntegrationEnvironment; -import org.terasology.engine.integrationenvironment.jupiter.MTEExtension; import org.terasology.engine.logic.players.LocalPlayer; import org.terasology.engine.network.NetworkMode; import org.terasology.engine.network.NetworkSystem; import static com.google.common.truth.Truth.assertThat; -@Tag("MteTest") -@ExtendWith(MTEExtension.class) @IntegrationEnvironment(networkMode = NetworkMode.LISTEN_SERVER) public class NetworkModeServerTest { @Test diff --git a/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/SubclassInjectionTest.java b/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/SubclassInjectionTest.java index b1d0b906085..7d10a43cb33 100644 --- a/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/SubclassInjectionTest.java +++ b/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/SubclassInjectionTest.java @@ -4,14 +4,11 @@ package org.terasology.engine.integrationenvironment; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.terasology.engine.integrationenvironment.fixtures.BaseTestingClass; -import org.terasology.engine.integrationenvironment.jupiter.MTEExtension; +import org.terasology.engine.integrationenvironment.jupiter.IntegrationEnvironment; -@Tag("MteTest") -@ExtendWith(MTEExtension.class) +@IntegrationEnvironment public class SubclassInjectionTest extends BaseTestingClass { @Test public void testInjection() { diff --git a/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/TestEventReceiverTest.java b/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/TestEventReceiverTest.java index f565f3ae754..2341c5c2d29 100644 --- a/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/TestEventReceiverTest.java +++ b/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/TestEventReceiverTest.java @@ -3,13 +3,11 @@ package org.terasology.engine.integrationenvironment; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.terasology.engine.context.Context; import org.terasology.engine.entitySystem.entity.EntityManager; import org.terasology.engine.entitySystem.entity.EntityRef; -import org.terasology.engine.integrationenvironment.jupiter.MTEExtension; +import org.terasology.engine.integrationenvironment.jupiter.IntegrationEnvironment; import org.terasology.engine.logic.location.LocationComponent; import org.terasology.engine.registry.In; import org.terasology.unittest.stubs.DummyComponent; @@ -20,8 +18,7 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BiConsumer; -@Tag("MteTest") -@ExtendWith(MTEExtension.class) +@IntegrationEnvironment public class TestEventReceiverTest { @In diff --git a/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/WorldProviderTest.java b/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/WorldProviderTest.java index 738190f8cd1..808205fe9da 100644 --- a/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/WorldProviderTest.java +++ b/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/WorldProviderTest.java @@ -4,16 +4,13 @@ import org.joml.Vector3i; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.terasology.engine.integrationenvironment.jupiter.MTEExtension; +import org.terasology.engine.integrationenvironment.jupiter.IntegrationEnvironment; import org.terasology.engine.registry.In; import org.terasology.engine.world.WorldProvider; import org.terasology.engine.world.block.BlockManager; -@Tag("MteTest") -@ExtendWith(MTEExtension.class) +@IntegrationEnvironment public class WorldProviderTest { @In diff --git a/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/delay/DelayManagerTest.java b/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/delay/DelayManagerTest.java index cb4b09b55ba..a480dee9697 100644 --- a/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/delay/DelayManagerTest.java +++ b/engine-tests/src/test/java/org/terasology/engine/integrationenvironment/delay/DelayManagerTest.java @@ -5,9 +5,7 @@ import com.google.common.collect.Lists; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.terasology.engine.core.Time; @@ -16,7 +14,6 @@ import org.terasology.engine.integrationenvironment.ModuleTestingHelper; import org.terasology.engine.integrationenvironment.TestEventReceiver; import org.terasology.engine.integrationenvironment.jupiter.IntegrationEnvironment; -import org.terasology.engine.integrationenvironment.jupiter.MTEExtension; import org.terasology.engine.logic.delay.DelayManager; import org.terasology.engine.logic.delay.DelayedActionTriggeredEvent; import org.terasology.engine.network.ClientComponent; @@ -25,8 +22,6 @@ import java.io.IOException; -@Tag("MteTest") -@ExtendWith(MTEExtension.class) @IntegrationEnvironment(networkMode = NetworkMode.LISTEN_SERVER) public class DelayManagerTest { private static final Logger logger = LoggerFactory.getLogger(DelayManagerTest.class); diff --git a/engine/src/main/java/org/terasology/engine/core/module/ModuleManager.java b/engine/src/main/java/org/terasology/engine/core/module/ModuleManager.java index 52a5a249944..7adedf161f9 100644 --- a/engine/src/main/java/org/terasology/engine/core/module/ModuleManager.java +++ b/engine/src/main/java/org/terasology/engine/core/module/ModuleManager.java @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 package org.terasology.engine.core.module; +import com.google.common.base.VerifyException; import com.google.common.collect.Sets; import org.reflections.Reflections; import org.reflections.scanners.Scanner; @@ -50,10 +51,13 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.PropertyPermission; import java.util.Set; import java.util.stream.Collectors; +import static com.google.common.base.Verify.verify; + public class ModuleManager { /** Set this property to "true" to allow modules on the classpath. */ public static final String LOAD_CLASSPATH_MODULES_PROPERTY = "org.terasology.load_classpath_modules"; @@ -277,11 +281,39 @@ private void setupSandbox() { * * @deprecated Use {@link #resolveAndLoadEnvironment} if you need module dependency resolution. */ - @Deprecated/*(since="4.4.0")*/ + @Deprecated(since = "4.4.0") public ModuleRegistry getRegistry() { return registry; } + /** + * Look up the module registered with this path. + *

+ * This queries modules that have already been registered. It does not register + * anything new for the path or make any addition to the current module search path. + * + * @return empty if no modules match the path + * @throws VerifyException if more than one module matches + */ + public Optional getModuleAt(Path path) { + final var matchingModules = new HashSet(); + final var absolutePath = path.toAbsolutePath().normalize(); + for (Module module : registry) { + for (Path modulePath : module.getResources().getRootPaths()) { + if (modulePath.toAbsolutePath().normalize().equals(absolutePath)) { + matchingModules.add(module); + } + } + } + if (matchingModules.isEmpty()) { + return Optional.empty(); + } else { + verify(matchingModules.size() == 1, + "Path {} matched multiple modules: {}", path, matchingModules); + return Optional.of(matchingModules.iterator().next()); + } + } + public ModuleInstallManager getInstallManager() { return installManager; } From 2dab9f6bdf6cdd61b3784cbeaf9c41a5599af5fe Mon Sep 17 00:00:00 2001 From: Nikitas Maragkos Date: Sat, 28 Jan 2023 15:14:53 +0200 Subject: [PATCH 013/100] refactor: head bobbing setting title Change "Bobbing" to "Head Bobbing" for more clarity. --- .../resources/org/terasology/engine/assets/i18n/menu_en.lang | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/src/main/resources/org/terasology/engine/assets/i18n/menu_en.lang b/engine/src/main/resources/org/terasology/engine/assets/i18n/menu_en.lang index ef58f6558c3..ef11b1edc13 100644 --- a/engine/src/main/resources/org/terasology/engine/assets/i18n/menu_en.lang +++ b/engine/src/main/resources/org/terasology/engine/assets/i18n/menu_en.lang @@ -64,7 +64,7 @@ "biome-humidity": "Humidity", "biome-name": "Name", "bloom-effect": "Bloom", - "bobbing": "Bobbing", + "bobbing": "Head Bobbing", "button-web-browser-confirmation-title": "A confirmation is required.", "button-web-browser-confirmation-message": "Are you sure you want to open this url in browser?", "camera-blur": "Blur", From a2cdbd5308da0ccca7291e33a306f223d07bb2b3 Mon Sep 17 00:00:00 2001 From: BenjaminAmos <24301287+BenjaminAmos@users.noreply.github.com> Date: Thu, 6 Apr 2023 03:04:30 +0100 Subject: [PATCH 014/100] build(distribution): re-enable SecurityManager in distributions running under Java 18 and above (#5091) --- facades/PC/src/main/startScripts/unixStartScript.gsp | 7 +++++++ .../PC/src/main/startScripts/windowsStartScript.bat.gsp | 8 ++++++++ 2 files changed, 15 insertions(+) diff --git a/facades/PC/src/main/startScripts/unixStartScript.gsp b/facades/PC/src/main/startScripts/unixStartScript.gsp index ee7249d5fd3..911308c49a8 100644 --- a/facades/PC/src/main/startScripts/unixStartScript.gsp +++ b/facades/PC/src/main/startScripts/unixStartScript.gsp @@ -169,6 +169,13 @@ save () { } APP_ARGS=`save "\$@"` +# Terasology-specific changes - Re-enable SecurityManager on Java 18+ +# According to https://openjdk.org/jeps/223, this string is intentionally parsable. +JAVA_VERSION=`java -fullversion 2>&1 | sed 's/.* //;s/"//;s/\\([0-9]*\\)\\..*/\\1/'` +if [ \$JAVA_VERSION -gt 17 ]; then + DEFAULT_JVM_OPTS="\$DEFAULT_JVM_OPTS -Djava.security.manager=allow" +fi + # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- \$DEFAULT_JVM_OPTS \$JAVA_OPTS \$${optsEnvironmentVar} <% if ( appNameSystemProperty ) { %>"\"-D${appNameSystemProperty}=\$APP_BASE_NAME\"" <% } %> <% if ( mainClassName.startsWith('--module ') ) { %>--module-path "\"\$MODULE_PATH\"" <% } %>-jar lib/Terasology.jar "\$APP_ARGS" diff --git a/facades/PC/src/main/startScripts/windowsStartScript.bat.gsp b/facades/PC/src/main/startScripts/windowsStartScript.bat.gsp index a4cf2790ac4..f33992fabd2 100644 --- a/facades/PC/src/main/startScripts/windowsStartScript.bat.gsp +++ b/facades/PC/src/main/startScripts/windowsStartScript.bat.gsp @@ -62,6 +62,14 @@ goto fail <% if ( mainClassName.startsWith('--module ') ) { %>set MODULE_PATH=$modulePath<% } %> +@rem Terasology-specific changes - Re-enable SecurityManager on Java 18+ +@rem According to https://openjdk.org/jeps/223, this string is intentionally parsable. +for /f "tokens=4 delims= " %%a in ('"%JAVA_EXE%" -fullversion 2^>^&1 1^>nul') do ( for /f "delims=." %%b in ('echo %%a') do set JAVA_VERSION="%%b" ) +set JAVA_VERSION=%JAVA_VERSION:"=% +if %JAVA_VERSION% gtr 17 ( + set DEFAULT_JVM_OPTS=%DEFAULT_JVM_OPTS% -Djava.security.manager=allow +) + @rem Execute ${applicationName} "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %${optsEnvironmentVar}% <% if ( appNameSystemProperty ) { %>"-D${appNameSystemProperty}=%APP_BASE_NAME%"<% } %> <% if ( mainClassName.startsWith('--module ') ) { %>--module-path "%MODULE_PATH%" <% } %>-jar lib\\Terasology.jar %* From 8943264ea1cf66e7df9ad7f463d5717454a08a77 Mon Sep 17 00:00:00 2001 From: Sylvia Favello <90116354+sfavello@users.noreply.github.com> Date: Mon, 10 Apr 2023 07:49:01 -0700 Subject: [PATCH 015/100] doc: replace LGTM badges with code climate (#5095) --- README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 2a5362e1359..ffec84cdf59 100644 --- a/README.md +++ b/README.md @@ -9,11 +9,14 @@ License (Art) - - LGTM Alerts + + Code climate maintainability - - Java Grade + + Code climate tech debt + + + Code climate issues From 4c177c68b141a990381eabeddbba5fb7c884fecb Mon Sep 17 00:00:00 2001 From: Sylvia Favello <90116354+sfavello@users.noreply.github.com> Date: Wed, 12 Apr 2023 10:17:24 -0700 Subject: [PATCH 016/100] ci: update autoclose file version and fix github username (#5097) * rephrase stale and close message to be more friendly * provide whitespace between the asperand symbol and github username --- .github/workflows/autoclose.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/autoclose.yml b/.github/workflows/autoclose.yml index b6e156cca11..e60ebec762f 100644 --- a/.github/workflows/autoclose.yml +++ b/.github/workflows/autoclose.yml @@ -10,14 +10,14 @@ jobs: issues: write pull-requests: write steps: - - uses: actions/stale@v4 + - uses: actions/stale@v8 with: only-labels: "Status: Needs Author Input" days-before-issue-stale: 30 days-before-issue-close: 90 stale-issue-label: "Status: Stale" - stale-issue-message: "This issue is stale because the author @${{ github.event.issue.user.login }} has not responded for 30 days." - close-issue-message: "This issue was closed because the author @${{ github.event.issue.user.login }} has not responded for 90 days since the issue was marked as stale." + stale-issue-message: "Hey there @ ${{ github.event.issue.user.login }} , we want to express our appreciation for your initial contribution to this issue. However, we now mark it as stale since we haven't received any response from you in the past 30 days. If you're still available, we would greatly appreciate it if you could provide answers to any open questions and/or share the requested feedback/input. Thank you for your consideration, we hope to hear from you soon!" + close-issue-message: "Hey there @ ${{ github.event.issue.user.login }} , we want to express our appreciation for your initial contribution to this issue. However, we now mark it as closed since we haven't received any response from you in the past 90 days. If you're still available, we would greatly appreciate it if you could provide answers to any open questions and/or share the requested feedback/input. Thank you for your consideration, we hope to hear from you soon!" close-issue-reason: "not_planned" days-before-pr-stale: -1 days-before-pr-close: -1 From 2404f94583375760c0f80fd984716460f57ad294 Mon Sep 17 00:00:00 2001 From: Benjamin Amos Date: Wed, 5 Jul 2023 22:14:40 +0100 Subject: [PATCH 017/100] fix: disable SecurityManager at runtime when using Java 18 and above --- .../org/terasology/engine/core/module/ModuleManager.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/engine/src/main/java/org/terasology/engine/core/module/ModuleManager.java b/engine/src/main/java/org/terasology/engine/core/module/ModuleManager.java index 7adedf161f9..5538bb2057c 100644 --- a/engine/src/main/java/org/terasology/engine/core/module/ModuleManager.java +++ b/engine/src/main/java/org/terasology/engine/core/module/ModuleManager.java @@ -272,8 +272,13 @@ private void setupSandbox() { permissionSet.grantPermission(new PropertyPermission("reactor.schedulers.defaultBoundedElasticQueueSize", "read")); } - Policy.setPolicy(new ModuleSecurityPolicy()); - System.setSecurityManager(new ModuleSecurityManager()); + if (Runtime.version().feature() < 18 || "allow".equals(System.getProperty("java.security.manager"))) { + Policy.setPolicy(new ModuleSecurityPolicy()); + System.setSecurityManager(new ModuleSecurityManager()); + } else { + logger.warn("SecurityManager is disabled starting with Java 18 - module sandbox functionality is limited!"); + logger.warn("To enable SecurityManager, use the \"-Djava.security.manager=allow\" JVM option."); + } } /** From c89f64a27527f745a7416c0e3f1d787f95d9bd08 Mon Sep 17 00:00:00 2001 From: Benjamin Amos Date: Wed, 5 Jul 2023 22:14:57 +0100 Subject: [PATCH 018/100] Revert "build(distribution): re-enable SecurityManager in distributions running under Java 18 and above" This reverts commit ca913261d6564ac552b067a234b6418c2a3a9ec7. --- facades/PC/src/main/startScripts/unixStartScript.gsp | 7 ------- .../PC/src/main/startScripts/windowsStartScript.bat.gsp | 8 -------- 2 files changed, 15 deletions(-) diff --git a/facades/PC/src/main/startScripts/unixStartScript.gsp b/facades/PC/src/main/startScripts/unixStartScript.gsp index 911308c49a8..ee7249d5fd3 100644 --- a/facades/PC/src/main/startScripts/unixStartScript.gsp +++ b/facades/PC/src/main/startScripts/unixStartScript.gsp @@ -169,13 +169,6 @@ save () { } APP_ARGS=`save "\$@"` -# Terasology-specific changes - Re-enable SecurityManager on Java 18+ -# According to https://openjdk.org/jeps/223, this string is intentionally parsable. -JAVA_VERSION=`java -fullversion 2>&1 | sed 's/.* //;s/"//;s/\\([0-9]*\\)\\..*/\\1/'` -if [ \$JAVA_VERSION -gt 17 ]; then - DEFAULT_JVM_OPTS="\$DEFAULT_JVM_OPTS -Djava.security.manager=allow" -fi - # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- \$DEFAULT_JVM_OPTS \$JAVA_OPTS \$${optsEnvironmentVar} <% if ( appNameSystemProperty ) { %>"\"-D${appNameSystemProperty}=\$APP_BASE_NAME\"" <% } %> <% if ( mainClassName.startsWith('--module ') ) { %>--module-path "\"\$MODULE_PATH\"" <% } %>-jar lib/Terasology.jar "\$APP_ARGS" diff --git a/facades/PC/src/main/startScripts/windowsStartScript.bat.gsp b/facades/PC/src/main/startScripts/windowsStartScript.bat.gsp index f33992fabd2..a4cf2790ac4 100644 --- a/facades/PC/src/main/startScripts/windowsStartScript.bat.gsp +++ b/facades/PC/src/main/startScripts/windowsStartScript.bat.gsp @@ -62,14 +62,6 @@ goto fail <% if ( mainClassName.startsWith('--module ') ) { %>set MODULE_PATH=$modulePath<% } %> -@rem Terasology-specific changes - Re-enable SecurityManager on Java 18+ -@rem According to https://openjdk.org/jeps/223, this string is intentionally parsable. -for /f "tokens=4 delims= " %%a in ('"%JAVA_EXE%" -fullversion 2^>^&1 1^>nul') do ( for /f "delims=." %%b in ('echo %%a') do set JAVA_VERSION="%%b" ) -set JAVA_VERSION=%JAVA_VERSION:"=% -if %JAVA_VERSION% gtr 17 ( - set DEFAULT_JVM_OPTS=%DEFAULT_JVM_OPTS% -Djava.security.manager=allow -) - @rem Execute ${applicationName} "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %${optsEnvironmentVar}% <% if ( appNameSystemProperty ) { %>"-D${appNameSystemProperty}=%APP_BASE_NAME%"<% } %> <% if ( mainClassName.startsWith('--module ') ) { %>--module-path "%MODULE_PATH%" <% } %>-jar lib\\Terasology.jar %* From 324376a044db3e7e0395c0396de1ec3f0311a203 Mon Sep 17 00:00:00 2001 From: jdrueckert Date: Sun, 9 Jul 2023 22:36:09 +0200 Subject: [PATCH 019/100] refactor: remove vr support (#5112) * remove OpenVRInput and usages * remove OpenVRControllers * remove OpenVRStereoCamera and usages * remove vr support from rendering config and usages * remove OpenVRProvider and usages * remove OpenVRState * remove OpenVRUtils and usages * remove ControllerListener and openvrprovider rendering package --- .../integrationenvironment/Engines.java | 4 +- .../engine/config/RenderingConfig.java | 14 +- .../engine/core/modes/StateIngame.java | 8 +- .../subsystem/openvr/OpenVRControllers.java | 158 ------ .../core/subsystem/openvr/OpenVRInput.java | 99 ---- ...irstPersonHeldItemMountPointComponent.java | 27 - .../logic/players/LocalPlayerSystem.java | 8 - .../rendering/cameras/OpenVRStereoCamera.java | 244 --------- .../videoSettings/VideoSettingsScreen.java | 1 - .../openvrprovider/ControllerListener.java | 37 -- .../openvrprovider/OpenVRProvider.java | 479 ------------------ .../rendering/openvrprovider/OpenVRState.java | 148 ------ .../rendering/openvrprovider/OpenVRUtil.java | 150 ------ .../openvrprovider/package-info.java | 6 - .../rendering/world/WorldRendererImpl.java | 35 +- .../engine/assets/ui/menu/videoMenuScreen.ui | 10 - .../org/terasology/engine/Terasology.java | 4 +- 17 files changed, 7 insertions(+), 1425 deletions(-) delete mode 100644 engine/src/main/java/org/terasology/engine/core/subsystem/openvr/OpenVRControllers.java delete mode 100644 engine/src/main/java/org/terasology/engine/core/subsystem/openvr/OpenVRInput.java delete mode 100644 engine/src/main/java/org/terasology/engine/rendering/cameras/OpenVRStereoCamera.java delete mode 100644 engine/src/main/java/org/terasology/engine/rendering/openvrprovider/ControllerListener.java delete mode 100644 engine/src/main/java/org/terasology/engine/rendering/openvrprovider/OpenVRProvider.java delete mode 100644 engine/src/main/java/org/terasology/engine/rendering/openvrprovider/OpenVRState.java delete mode 100644 engine/src/main/java/org/terasology/engine/rendering/openvrprovider/OpenVRUtil.java delete mode 100644 engine/src/main/java/org/terasology/engine/rendering/openvrprovider/package-info.java diff --git a/engine-tests/src/main/java/org/terasology/engine/integrationenvironment/Engines.java b/engine-tests/src/main/java/org/terasology/engine/integrationenvironment/Engines.java index b061cb7e6a9..1e635c26947 100644 --- a/engine-tests/src/main/java/org/terasology/engine/integrationenvironment/Engines.java +++ b/engine-tests/src/main/java/org/terasology/engine/integrationenvironment/Engines.java @@ -30,7 +30,6 @@ import org.terasology.engine.core.subsystem.lwjgl.LwjglGraphics; import org.terasology.engine.core.subsystem.lwjgl.LwjglInput; import org.terasology.engine.core.subsystem.lwjgl.LwjglTimer; -import org.terasology.engine.core.subsystem.openvr.OpenVRInput; import org.terasology.engine.integrationenvironment.jupiter.IntegrationEnvironment; import org.terasology.engine.integrationenvironment.jupiter.MTEExtension; import org.terasology.engine.network.JoinStatus; @@ -200,8 +199,7 @@ TerasologyEngine createHeadedEngine() throws IOException { .add(audio) .add(new LwjglGraphics()) .add(new LwjglTimer()) - .add(new LwjglInput()) - .add(new OpenVRInput()); + .add(new LwjglInput()); createExtraSubsystems().forEach(terasologyEngineBuilder::add); return createEngine(terasologyEngineBuilder); diff --git a/engine/src/main/java/org/terasology/engine/config/RenderingConfig.java b/engine/src/main/java/org/terasology/engine/config/RenderingConfig.java index 2918bc0979e..881cebb1679 100644 --- a/engine/src/main/java/org/terasology/engine/config/RenderingConfig.java +++ b/engine/src/main/java/org/terasology/engine/config/RenderingConfig.java @@ -44,7 +44,6 @@ public class RenderingConfig extends AbstractSubscribable { public static final String LIGHT_SHAFTS = "LightShafts"; public static final String EYE_ADAPTATION = "EyeAdaptation"; public static final String BLOOM = "Bloom"; - public static final String VR_SUPPORT = "VrSupport"; public static final String MAX_TEXTURE_ATLAS_RESOLUTION = "MaxTextureAtlasResolution"; public static final String MAX_CHUNKS_USED_FOR_SHADOW_MAPPING = "MaxChunksUsedForShadowMapping"; public static final String SHADOW_MAP_RESOLUTION = "ShadowMapResolution"; @@ -95,7 +94,6 @@ public class RenderingConfig extends AbstractSubscribable { private boolean eyeAdaptation; private boolean bloom; private boolean dynamicShadows; - private boolean vrSupport; private int maxTextureAtlasResolution; private int maxChunksUsedForShadowMapping; private int shadowMapResolution; @@ -387,7 +385,7 @@ public void setVignette(boolean vignette) { } public boolean isMotionBlur() { - return motionBlur && !isVrSupport(); + return motionBlur; } public void setMotionBlur(boolean motionBlur) { @@ -456,16 +454,6 @@ public void setBloom(boolean bloom) { propertyChangeSupport.firePropertyChange(BLOOM, oldValue, this.bloom); } - public boolean isVrSupport() { - return vrSupport; - } - - public void setVrSupport(boolean vrSupport) { - boolean oldValue = this.vrSupport; - this.vrSupport = vrSupport; - propertyChangeSupport.firePropertyChange(VR_SUPPORT, oldValue, this.vrSupport); - } - public int getMaxTextureAtlasResolution() { return maxTextureAtlasResolution; } diff --git a/engine/src/main/java/org/terasology/engine/core/modes/StateIngame.java b/engine/src/main/java/org/terasology/engine/core/modes/StateIngame.java index 2660c3ae33f..34a102e4a5b 100644 --- a/engine/src/main/java/org/terasology/engine/core/modes/StateIngame.java +++ b/engine/src/main/java/org/terasology/engine/core/modes/StateIngame.java @@ -219,12 +219,8 @@ public void render() { display.prepareToRender(); if (worldRenderer != null) { - if (!context.get(Config.class).getRendering().isVrSupport()) { - worldRenderer.render(RenderingStage.MONO); - } else { - worldRenderer.render(RenderingStage.LEFT_EYE); - worldRenderer.render(RenderingStage.RIGHT_EYE); - } + worldRenderer.render(RenderingStage.LEFT_EYE); + worldRenderer.render(RenderingStage.RIGHT_EYE); } /* UI */ diff --git a/engine/src/main/java/org/terasology/engine/core/subsystem/openvr/OpenVRControllers.java b/engine/src/main/java/org/terasology/engine/core/subsystem/openvr/OpenVRControllers.java deleted file mode 100644 index 87f75259192..00000000000 --- a/engine/src/main/java/org/terasology/engine/core/subsystem/openvr/OpenVRControllers.java +++ /dev/null @@ -1,158 +0,0 @@ -// Copyright 2021 The Terasology Foundation -// SPDX-License-Identifier: Apache-2.0 -package org.terasology.engine.core.subsystem.openvr; - -import jopenvr.VRControllerState_t; -import org.joml.Matrix4f; -import org.terasology.engine.rendering.openvrprovider.ControllerListener; -import org.terasology.engine.rendering.openvrprovider.OpenVRUtil; -import org.terasology.input.ButtonState; -import org.terasology.input.ControllerDevice; -import org.terasology.input.ControllerId; -import org.terasology.input.InputType; -import org.terasology.input.device.ControllerAction; - -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.List; -import java.util.Queue; - -/** - * This class acts as an interface between OpenVR controllers and Terasology controllers. By implementing - * ControllerListener.buttonStateChanged(), OpenVR controller events make it into this class and are stored. Then, - * by implementing getInputQueue(), an overridden method from ControllerDevice, the Terasology controller system - * frequently retrieves and handles these events. - */ -class OpenVRControllers implements ControllerDevice, ControllerListener { - - private Queue queuedActions = new ArrayDeque<>(); - private VRControllerState_t cachedStateBefore; - private VRControllerState_t cachedStateAfter; - - /** - * Get the controller names provided by this ControllerDevice. - * @return the list of controllers names. - */ - @Override - public List getControllers() { - List ids = new ArrayList<>(); - ids.add("OpenVR"); - return ids; - } - - /** - * Get all queued actions registered since this method was last called. - * @return a queue of actions. - */ - @Override - public Queue getInputQueue() { - Queue result = new ArrayDeque<>(queuedActions); - queuedActions.clear(); - return result; - } - - private boolean buttonUp(long buttonIndex) { - return OpenVRUtil.switchedUp(buttonIndex, cachedStateBefore.ulButtonPressed, cachedStateAfter.ulButtonPressed); - } - - private boolean buttonDown(long buttonIndex) { - return OpenVRUtil.switchedDown(buttonIndex, cachedStateBefore.ulButtonPressed, cachedStateAfter.ulButtonPressed); - } - - private void addAxisAction(int controllerButton, ButtonState buttonState, float axisValue) { - queuedActions.add(new ControllerAction(InputType.CONTROLLER_AXIS.getInput(controllerButton), - "OpenVR", buttonState, axisValue)); - } - - private void addButtonAction(int controllerButton, ButtonState buttonState) { - queuedActions.add(new ControllerAction(InputType.CONTROLLER_BUTTON.getInput(controllerButton), - "OpenVR", buttonState, 1.0f)); - } - - private void handleController0() { - if (buttonUp(ControllerListener.TRIGGER_BUTTON)) { - addButtonAction(ControllerId.ZERO, ButtonState.UP); - } else if (buttonDown(ControllerListener.TRIGGER_BUTTON)) { - addButtonAction(ControllerId.ZERO, ButtonState.DOWN); - } else if (buttonUp(ControllerListener.GRIP_BUTTON)) { - addButtonAction(ControllerId.ONE, ButtonState.UP); - } else if (buttonDown(ControllerListener.GRIP_BUTTON)) { - addButtonAction(ControllerId.ONE, ButtonState.DOWN); - } else if (buttonUp(ControllerListener.APP_MENU_BUTTON)) { - addButtonAction(ControllerId.TWO, ButtonState.UP); - } else if (buttonDown(ControllerListener.APP_MENU_BUTTON)) { - addButtonAction(ControllerId.TWO, ButtonState.DOWN); - } else if (buttonDown(ControllerListener.TOUCHPAD_BUTTON)) { - addAxisAction(ControllerId.X_AXIS, ButtonState.DOWN, -cachedStateAfter.rAxis[0].x); - addAxisAction(ControllerId.Y_AXIS, ButtonState.DOWN, cachedStateAfter.rAxis[0].y); - } else if (buttonUp(ControllerListener.TOUCHPAD_BUTTON)) { - addAxisAction(ControllerId.X_AXIS, ButtonState.UP, 0.0f); - addAxisAction(ControllerId.Y_AXIS, ButtonState.UP, 0.0f); - } - } - - private void handleController1() { - if (buttonUp(ControllerListener.TRIGGER_BUTTON)) { - addButtonAction(ControllerId.THREE, ButtonState.UP); - } else if (buttonDown(ControllerListener.TRIGGER_BUTTON)) { - addButtonAction(ControllerId.THREE, ButtonState.DOWN); - } else if (buttonUp(ControllerListener.GRIP_BUTTON)) { - addButtonAction(ControllerId.FOUR, ButtonState.UP); - } else if (buttonDown(ControllerListener.GRIP_BUTTON)) { - addButtonAction(ControllerId.FOUR, ButtonState.DOWN); - } else if (buttonUp(ControllerListener.APP_MENU_BUTTON)) { - addButtonAction(ControllerId.FIVE, ButtonState.UP); - } else if (buttonDown(ControllerListener.APP_MENU_BUTTON)) { - addButtonAction(ControllerId.FIVE, ButtonState.DOWN); - } else if (buttonDown(ControllerListener.TOUCHPAD_BUTTON)) { - if (cachedStateAfter.rAxis[0].x < 0 && cachedStateAfter.rAxis[0].y < 0) { - addButtonAction(ControllerId.SIX, ButtonState.DOWN); - } else if (cachedStateAfter.rAxis[0].x > 0 && cachedStateAfter.rAxis[0].y < 0) { - addButtonAction(ControllerId.SEVEN, ButtonState.DOWN); - } else if (cachedStateAfter.rAxis[0].x < 0 && cachedStateAfter.rAxis[0].y < 0) { - addButtonAction(ControllerId.EIGHT, ButtonState.DOWN); - } else if (cachedStateAfter.rAxis[0].x > 0 && cachedStateAfter.rAxis[0].y > 0) { - addButtonAction(ControllerId.NINE, ButtonState.DOWN); - } - } else if (buttonUp(ControllerListener.TOUCHPAD_BUTTON)) { - if (cachedStateAfter.rAxis[0].x < 0 && cachedStateAfter.rAxis[0].y < 0) { - addButtonAction(ControllerId.SIX, ButtonState.UP); - } else if (cachedStateAfter.rAxis[0].x > 0 && cachedStateAfter.rAxis[0].y < 0) { - addButtonAction(ControllerId.SEVEN, ButtonState.UP); - } else if (cachedStateAfter.rAxis[0].x < 0 && cachedStateAfter.rAxis[0].y < 0) { - addButtonAction(ControllerId.EIGHT, ButtonState.UP); - } else if (cachedStateAfter.rAxis[0].x > 0 && cachedStateAfter.rAxis[0].y > 0) { - addButtonAction(ControllerId.NINE, ButtonState.UP); - } - } - } - - /** - * Called whenever the OpenVR controller button state changes for a given controller (left or right). - * @param stateBefore - the state before the last change. - * @param stateAfter - the state after the last change. - * @param handIndex - the hand index, an integer - 0 for left, 1 for right. - */ - @Override - public void buttonStateChanged(VRControllerState_t stateBefore, VRControllerState_t stateAfter, int handIndex) { - cachedStateBefore = stateBefore; - cachedStateAfter = stateAfter; - if (handIndex == 0) { - handleController0(); - } else { - handleController1(); - } - } - - /** - * Called whenever the OpenVR controller pose changes for a given controller (left or right). This particular - * listener just ignores pose updates. - * @param pose - the pose of the controller (a 4x4 matrix). - * @param handIndex - the hand index, an integer - 0 for left, 1 for right. - */ - @Override - public void poseChanged(Matrix4f pose, int handIndex) { - // currently no actions are sensitive to controller movement - } - -} diff --git a/engine/src/main/java/org/terasology/engine/core/subsystem/openvr/OpenVRInput.java b/engine/src/main/java/org/terasology/engine/core/subsystem/openvr/OpenVRInput.java deleted file mode 100644 index 65e245629b6..00000000000 --- a/engine/src/main/java/org/terasology/engine/core/subsystem/openvr/OpenVRInput.java +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright 2021 The Terasology Foundation -// SPDX-License-Identifier: Apache-2.0 -package org.terasology.engine.core.subsystem.openvr; - -import org.lwjgl.glfw.GLFW; -import org.terasology.engine.config.Config; -import org.terasology.engine.context.Context; -import org.terasology.engine.core.modes.GameState; -import org.terasology.engine.core.subsystem.EngineSubsystem; -import org.terasology.engine.input.InputSystem; -import org.terasology.engine.input.lwjgl.LwjglKeyboardDevice; -import org.terasology.engine.input.lwjgl.LwjglMouseDevice; -import org.terasology.engine.rendering.openvrprovider.OpenVRProvider; -import org.terasology.gestalt.assets.module.ModuleAwareAssetTypeManager; - -public class OpenVRInput implements EngineSubsystem { - - Config config; - - private Context context; - - private OpenVRProvider vrProvider; - - private OpenVRControllers controllerDevice; - - /** - * Get the name of this subsystem. - * @return the name of the subsystem, a string. - */ - @Override - public String getName() { - return "OpenVRInput"; - } - - /** - * Actions that need to be performed before initialization. In this case, the VR provider is retrieved (and this - * possibly triggers an initialization if it hasn't been retrieved before). - * @param rootContext The root context, that will survive the entire run of the engine - */ - @Override - public void preInitialise(Context rootContext) { - context = rootContext; - vrProvider = OpenVRProvider.getInstance(); - } - - /** - * - * @param assetTypeManager The asset type manager to register asset types to - */ - @Override - public void registerCoreAssetTypes(ModuleAwareAssetTypeManager assetTypeManager) { - } - - /** - * Set up listeners and input devices. - * @param rootContext - */ - @Override - public void postInitialise(Context rootContext) { - config = context.get(Config.class); - if (!config.getRendering().isVrSupport()) { - return; - } - this.context = rootContext; - InputSystem inputSystem = context.get(InputSystem.class); - if (inputSystem == null) { - inputSystem = new InputSystem(); - inputSystem.setMouseDevice(new LwjglMouseDevice(config.getRendering())); - inputSystem.setKeyboardDevice(new LwjglKeyboardDevice()); - context.put(InputSystem.class, inputSystem); - - long window = GLFW.glfwGetCurrentContext(); - ((LwjglKeyboardDevice) inputSystem.getKeyboard()).registerToLwjglWindow(window); - ((LwjglMouseDevice) inputSystem.getMouseDevice()).registerToLwjglWindow(window); - } - - controllerDevice = new OpenVRControllers(); - vrProvider.getState().addControllerListener(controllerDevice); - inputSystem.setControllerDevice(controllerDevice); - } - - /** - * Tasks to perform after an update. - * @param currentState The current state - * @param delta The total time this frame/update cycle - */ - @Override - public void postUpdate(GameState currentState, float delta) { - currentState.handleInput(delta); - } - - /** - * Clean up all objects in this class. - */ - @Override - public void shutdown() { - vrProvider.getState().removeControllerListener(controllerDevice); - } -} diff --git a/engine/src/main/java/org/terasology/engine/logic/players/FirstPersonHeldItemMountPointComponent.java b/engine/src/main/java/org/terasology/engine/logic/players/FirstPersonHeldItemMountPointComponent.java index 7b5363df325..d7280dc5133 100644 --- a/engine/src/main/java/org/terasology/engine/logic/players/FirstPersonHeldItemMountPointComponent.java +++ b/engine/src/main/java/org/terasology/engine/logic/players/FirstPersonHeldItemMountPointComponent.java @@ -53,31 +53,4 @@ public void copyFrom(FirstPersonHeldItemMountPointComponent other) { this.trackingDataReceived = other.trackingDataReceived; this.toolAdjustmentMatrix = new Matrix4f(other.toolAdjustmentMatrix); } - - /** - * A callback target for the controller listener. When this callback triggers, the pos of the mount point will - * cuange to the value of the pose parameter. This is mainly designed as a callback, and not intended to be part - * of the public interface of this class. - * @param pose - the controller pose - a homogenous transformation matrix. - * @param handIndex - the hand index - 0 for left and 1 for right. - */ - //TODO: commented out due to a natives issue and VR not working at the moment anyway - /* - public void poseChanged(Matrix4f pose, int handIndex) { - // do nothing for the second controller - // TODO: put a hand for the second controller. - if (handIndex != 0) { - return; - } - trackingDataReceived = true; - Matrix4f adjustedPose = pose.mul(toolAdjustmentMatrix); - translate = new Vector3f(adjustedPose.m30(), adjustedPose.m31(), adjustedPose.m32()); - Vector4f jomlQuaternion = org.terasology.rendering.openvrprovider.OpenVRUtil.convertToQuaternion(adjustedPose); - if (rotationQuaternion == null) { - rotationQuaternion = new Quat4f(jomlQuaternion.x, jomlQuaternion.y, jomlQuaternion.z, jomlQuaternion.w); - } else { - rotationQuaternion.set(jomlQuaternion.x, jomlQuaternion.y, jomlQuaternion.z, jomlQuaternion.w); - } - } - */ } diff --git a/engine/src/main/java/org/terasology/engine/logic/players/LocalPlayerSystem.java b/engine/src/main/java/org/terasology/engine/logic/players/LocalPlayerSystem.java index 5b9fa751251..8f56649752d 100644 --- a/engine/src/main/java/org/terasology/engine/logic/players/LocalPlayerSystem.java +++ b/engine/src/main/java/org/terasology/engine/logic/players/LocalPlayerSystem.java @@ -150,10 +150,6 @@ private void processInput(EntityRef entity, CharacterMovementComponent character switch (characterMovementComponent.mode) { case CROUCHING: case WALKING: - if (!config.getRendering().isVrSupport()) { - viewRotation.rotationYXZ(Math.toRadians(lookYaw), 0, 0); - playerCamera.setOrientation(viewRotation); - } playerCamera.getOrientation(new Quaternionf()).transform(relMove); break; case CLIMBING: @@ -161,10 +157,6 @@ private void processInput(EntityRef entity, CharacterMovementComponent character relMove.y += relativeMovement.y; break; default: - if (!config.getRendering().isVrSupport()) { - viewRotation.rotationYXZ(Math.toRadians(lookYaw), Math.toRadians(lookPitch), 0); - playerCamera.setOrientation(viewRotation); - } playerCamera.getOrientation(new Quaternionf()).transform(relMove); relMove.y += relativeMovement.y; break; diff --git a/engine/src/main/java/org/terasology/engine/rendering/cameras/OpenVRStereoCamera.java b/engine/src/main/java/org/terasology/engine/rendering/cameras/OpenVRStereoCamera.java deleted file mode 100644 index 8542f113172..00000000000 --- a/engine/src/main/java/org/terasology/engine/rendering/cameras/OpenVRStereoCamera.java +++ /dev/null @@ -1,244 +0,0 @@ -// Copyright 2021 The Terasology Foundation -// SPDX-License-Identifier: Apache-2.0 -package org.terasology.engine.rendering.cameras; - -import org.joml.FrustumIntersection; -import org.joml.Matrix4f; -import org.joml.Quaternionf; -import org.joml.Vector4f; -import org.terasology.engine.registry.CoreRegistry; -import org.terasology.engine.rendering.openvrprovider.OpenVRProvider; -import org.terasology.engine.rendering.openvrprovider.OpenVRUtil; -import org.terasology.engine.rendering.world.WorldRenderer; -import org.terasology.engine.rendering.world.WorldRenderer.RenderingStage; - -/** - * Camera which can be used to render stereoscopic images of the scene for VR. - */ -public class OpenVRStereoCamera extends Camera { - - private final Matrix4f projectionMatrixLeftEye = new Matrix4f(); - private final Matrix4f projectionMatrixRightEye = new Matrix4f(); - - private final Matrix4f inverseProjectionMatrixLeftEye = new Matrix4f(); - private final Matrix4f inverseProjectionMatrixRightEye = new Matrix4f(); - - private final Matrix4f inverseViewProjectionMatrixLeftEye = new Matrix4f(); - private final Matrix4f inverseViewProjectionMatrixRightEye = new Matrix4f(); - - private final Matrix4f viewMatrixLeftEye = new Matrix4f(); - private final Matrix4f viewMatrixRightEye = new Matrix4f(); - - private final Matrix4f viewMatrixReflectedLeftEye = new Matrix4f(); - private final Matrix4f viewMatrixReflectedRightEye = new Matrix4f(); - - private final FrustumIntersection viewFrustumLeftEye = new FrustumIntersection(); - private final FrustumIntersection viewFrustumRightEye = new FrustumIntersection(); - private final FrustumIntersection viewFrustumReflectedLeftEye = new FrustumIntersection(); - private final FrustumIntersection viewFrustumReflectedRightEye = new FrustumIntersection(); - - private final Matrix4f viewProjectionMatrixLeftEye = new Matrix4f(); - private final Matrix4f viewProjectionMatrixRightEye = new Matrix4f(); - - private final Matrix4f viewTranslationLeftEye = new Matrix4f(); - private final Matrix4f viewTranslationRightEye = new Matrix4f(); - private final OpenVRProvider vrProvider; - - public OpenVRStereoCamera(OpenVRProvider provider) { - vrProvider = provider; - // OpenVR's projection matrix is such that this is approximately true. - zFar = 400.0f; - } - - @Override - public void updateFrustum() { - super.updateFrustum(); - - Matrix4f dest = new Matrix4f(); - - viewFrustumLeftEye.set(projectionMatrixLeftEye.mul(viewMatrixLeftEye, dest), true); - viewFrustumRightEye.set(projectionMatrixRightEye.mul(viewMatrixRightEye, dest)); - viewFrustumReflectedLeftEye.set(projectionMatrixLeftEye.mul(viewMatrixReflectedLeftEye, dest), true); - viewFrustumReflectedRightEye.set(projectionMatrixRightEye.mul(viewMatrixReflectedRightEye, dest), true); - } - - @Override - public boolean isBobbingAllowed() { - return false; - } - - @Override - public FrustumIntersection getViewFrustum() { - RenderingStage renderingStage = CoreRegistry.get(WorldRenderer.class).getCurrentRenderStage(); - - if (renderingStage == RenderingStage.LEFT_EYE) { - return viewFrustumLeftEye; - } else if (renderingStage == RenderingStage.RIGHT_EYE) { - return viewFrustumRightEye; - } - - return null; - } - - @Override - public FrustumIntersection getViewFrustumReflected() { - RenderingStage renderingStage = CoreRegistry.get(WorldRenderer.class).getCurrentRenderStage(); - - if (renderingStage == RenderingStage.LEFT_EYE) { - return viewFrustumReflectedLeftEye; - } else if (renderingStage == RenderingStage.RIGHT_EYE) { - return viewFrustumReflectedRightEye; - } - - return null; - } - - @Override - public Matrix4f getViewProjectionMatrix() { - RenderingStage renderingStage = CoreRegistry.get(WorldRenderer.class).getCurrentRenderStage(); - - if (renderingStage == RenderingStage.LEFT_EYE) { - return viewProjectionMatrixLeftEye; - } else if (renderingStage == RenderingStage.RIGHT_EYE) { - return viewProjectionMatrixRightEye; - } - - return null; - } - - @Override - public Matrix4f getViewMatrix() { - RenderingStage renderingStage = CoreRegistry.get(WorldRenderer.class).getCurrentRenderStage(); - - if (renderingStage == RenderingStage.LEFT_EYE) { - if (!isReflected()) { - return viewMatrixLeftEye; - } - return viewMatrixReflectedLeftEye; - } else if (renderingStage == RenderingStage.RIGHT_EYE) { - if (!isReflected()) { - return viewMatrixRightEye; - } - return viewMatrixReflectedRightEye; - } - - return null; - } - - @Override - public Matrix4f getProjectionMatrix() { - RenderingStage renderingStage = CoreRegistry.get(WorldRenderer.class).getCurrentRenderStage(); - - if (renderingStage == RenderingStage.LEFT_EYE) { - return projectionMatrixLeftEye; - } else if (renderingStage == RenderingStage.RIGHT_EYE) { - return projectionMatrixRightEye; - } - - return null; - } - - @Override - public Matrix4f getInverseProjectionMatrix() { - RenderingStage renderingStage = CoreRegistry.get(WorldRenderer.class).getCurrentRenderStage(); - - if (renderingStage == RenderingStage.LEFT_EYE) { - return inverseProjectionMatrixLeftEye; - } else if (renderingStage == RenderingStage.RIGHT_EYE) { - return inverseProjectionMatrixRightEye; - } - - return null; - } - - @Override - public Matrix4f getInverseViewProjectionMatrix() { - RenderingStage renderingStage = CoreRegistry.get(WorldRenderer.class).getCurrentRenderStage(); - - if (renderingStage == RenderingStage.LEFT_EYE) { - return inverseViewProjectionMatrixLeftEye; - } else if (renderingStage == RenderingStage.RIGHT_EYE) { - return inverseViewProjectionMatrixRightEye; - } - - return null; - } - - - @Override - public void update(float deltaT) { - super.update(deltaT); - updateMatrices(); - } - - @Override - public void updateMatrices() { - updateMatrices(activeFov); - } - - @Override - public void updateMatrices(float fov) { - prevViewProjectionMatrix.set(viewProjectionMatrix); - - Matrix4f leftEyeProjection = vrProvider.getState().getEyeProjectionMatrix(0); - Matrix4f rightEyeProjection = vrProvider.getState().getEyeProjectionMatrix(1); - Matrix4f leftEyePose = vrProvider.getState().getEyePose(0); - Matrix4f rightEyePose = vrProvider.getState().getEyePose(1); - float halfIPD = (float) Math.sqrt(Math.pow(leftEyePose.m30() - rightEyePose.m30(), 2) - + Math.pow(leftEyePose.m31() - rightEyePose.m31(), 2) - + Math.pow(leftEyePose.m32() - rightEyePose.m32(), 2)) / 2.0f; - - // set camera orientation - Vector4f vecQuaternion = OpenVRUtil.convertToQuaternion(leftEyePose); - Quaternionf quaternion = new Quaternionf(vecQuaternion.x, vecQuaternion.y, vecQuaternion.z, vecQuaternion.w); - setOrientation(quaternion); - - - leftEyePose = leftEyePose.invert(); // view matrix is inverse of pose matrix - rightEyePose = rightEyePose.invert(); - - - if (Math.sqrt(Math.pow(leftEyePose.m30(), 2) + Math.pow(leftEyePose.m31(), 2) + Math.pow(leftEyePose.m32(), - 2)) < 0.25) { - return; - } - projectionMatrixLeftEye.set(leftEyeProjection); - projectionMatrixRightEye.set(rightEyeProjection); - projectionMatrix = projectionMatrixLeftEye; - - viewMatrixLeftEye.set(leftEyePose); - viewMatrixRightEye.set(rightEyePose); - - viewMatrix = viewMatrixLeftEye; - normViewMatrix = viewMatrixLeftEye; - - reflectionMatrix.setRow(0, new Vector4f(1.0f, 0.0f, 0.0f, 0.0f)); - reflectionMatrix.setRow(1, new Vector4f(0.0f, -1.0f, 0.0f, 2f * (-position.y + 32f))); - reflectionMatrix.setRow(2, new Vector4f(0.0f, 0.0f, 1.0f, 0.0f)); - reflectionMatrix.setRow(3, new Vector4f(0.0f, 0.0f, 0.0f, 1.0f)); - viewMatrix.mul(reflectionMatrix, viewMatrixReflected); - - reflectionMatrix.setRow(1, new Vector4f(0.0f, -1.0f, 0.0f, 0.0f)); - normViewMatrix.mul(reflectionMatrix, normViewMatrixReflected); - - viewTranslationLeftEye.identity(); - viewTranslationLeftEye.setTranslation(halfIPD, 0.0f, 0.0f); - - viewTranslationRightEye.identity(); - viewTranslationRightEye.setTranslation(-halfIPD, 0.0f, 0.0f); - - viewTranslationLeftEye.mul(viewMatrixReflected, viewMatrixReflectedLeftEye); - viewTranslationRightEye.mul(viewMatrixReflected, viewMatrixReflectedRightEye); - - projectionMatrixLeftEye.mul(viewMatrixLeftEye, viewProjectionMatrixLeftEye); - projectionMatrixRightEye.mul(viewMatrixRightEye, viewProjectionMatrixRightEye); - - viewProjectionMatrixLeftEye.invert(inverseViewProjectionMatrixLeftEye); - viewProjectionMatrixRightEye.invert(inverseViewProjectionMatrixRightEye); - - projectionMatrixLeftEye.invert(inverseProjectionMatrixLeftEye); - projectionMatrixRightEye.invert(inverseProjectionMatrixRightEye); - - updateFrustum(); - } -} diff --git a/engine/src/main/java/org/terasology/engine/rendering/nui/layers/mainMenu/videoSettings/VideoSettingsScreen.java b/engine/src/main/java/org/terasology/engine/rendering/nui/layers/mainMenu/videoSettings/VideoSettingsScreen.java index 82898f23c8e..36ac41975d7 100644 --- a/engine/src/main/java/org/terasology/engine/rendering/nui/layers/mainMenu/videoSettings/VideoSettingsScreen.java +++ b/engine/src/main/java/org/terasology/engine/rendering/nui/layers/mainMenu/videoSettings/VideoSettingsScreen.java @@ -291,7 +291,6 @@ public void set(Resolution value) { } WidgetUtil.tryBindCheckbox(this, "menu-animations", BindHelper.bindBeanProperty("animatedMenu", config.getRendering(), Boolean.TYPE)); - WidgetUtil.tryBindCheckbox(this, "oculusVrSupport", BindHelper.bindBeanProperty("vrSupport", config.getRendering(), Boolean.TYPE)); WidgetUtil.tryBindCheckbox(this, "animateGrass", BindHelper.bindBeanProperty("animateGrass", config.getRendering(), Boolean.TYPE)); WidgetUtil.tryBindCheckbox(this, "animateWater", BindHelper.bindBeanProperty("animateWater", config.getRendering(), Boolean.TYPE)); WidgetUtil.tryBindCheckbox(this, "volumetricFog", BindHelper.bindBeanProperty("volumetricFog", config.getRendering(), Boolean.TYPE)); diff --git a/engine/src/main/java/org/terasology/engine/rendering/openvrprovider/ControllerListener.java b/engine/src/main/java/org/terasology/engine/rendering/openvrprovider/ControllerListener.java deleted file mode 100644 index a17365a244e..00000000000 --- a/engine/src/main/java/org/terasology/engine/rendering/openvrprovider/ControllerListener.java +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2021 The Terasology Foundation -// SPDX-License-Identifier: Apache-2.0 -package org.terasology.engine.rendering.openvrprovider; - -import jopenvr.JOpenVRLibrary; -import jopenvr.VRControllerState_t; -import org.joml.Matrix4f; - -/** - * Interface intended to be front-facing to user for controller interaction. - */ -public interface ControllerListener { - int LEFT_CONTROLLER = 0; - int RIGHT_CONTROLLER = 1; - int EAXIS_TRIGGER = 1; - int EAXIS_TOUCHPAD = 0; - long TOUCHPAD_BUTTON = (1L << JOpenVRLibrary.EVRButtonId.EVRButtonId_k_EButton_SteamVR_Touchpad); - long TRIGGER_BUTTON = (1L << JOpenVRLibrary.EVRButtonId.EVRButtonId_k_EButton_SteamVR_Trigger); - long APP_MENU_BUTTON = (1L << JOpenVRLibrary.EVRButtonId.EVRButtonId_k_EButton_ApplicationMenu); - long GRIP_BUTTON = (1L << JOpenVRLibrary.EVRButtonId.EVRButtonId_k_EButton_Grip); - float TRIGGER_THRESHOLD = .25f; - - /** - * Override this method with a handler for whenever the state of the OpenVR controller changes. - * @param stateBefore - the controller state before the change. - * @param stateAfter - the controller state after the change. - * @param handIndex - the hand index of the affected controller, an integer. 0 for the left hand, 1 for the right. - */ - void buttonStateChanged(VRControllerState_t stateBefore, VRControllerState_t stateAfter, int handIndex); - - /** - * Override this method with a handler for whenever the pose of the OpenVR controller changes. - * @param pose - the pose of the controller at the point of update, a 4x4 homogenous transformation matrix. - * @param handIndex - the hand index of the affected controller, an integer. 0 for the left hand, 1 for the right. - */ - void poseChanged(Matrix4f pose, int handIndex); -} diff --git a/engine/src/main/java/org/terasology/engine/rendering/openvrprovider/OpenVRProvider.java b/engine/src/main/java/org/terasology/engine/rendering/openvrprovider/OpenVRProvider.java deleted file mode 100644 index b14507ba980..00000000000 --- a/engine/src/main/java/org/terasology/engine/rendering/openvrprovider/OpenVRProvider.java +++ /dev/null @@ -1,479 +0,0 @@ -// Copyright 2021 The Terasology Foundation -// SPDX-License-Identifier: Apache-2.0 -package org.terasology.engine.rendering.openvrprovider; - -import com.sun.jna.Memory; -import com.sun.jna.NativeLibrary; -import com.sun.jna.Pointer; -import jopenvr.HmdMatrix34_t; -import jopenvr.HmdMatrix44_t; -import jopenvr.JOpenVRLibrary; -import jopenvr.JOpenVRLibrary.EVREventType; -import jopenvr.Texture_t; -import jopenvr.TrackedDevicePose_t; -import jopenvr.VRControllerState_t; -import jopenvr.VRTextureBounds_t; -import jopenvr.VR_IVRCompositor_FnTable; -import jopenvr.VR_IVROverlay_FnTable; -import jopenvr.VR_IVRSettings_FnTable; -import jopenvr.VR_IVRSystem_FnTable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.terasology.engine.utilities.OS; - -import java.nio.IntBuffer; -import java.nio.file.Paths; - -/** - * This class is designed to make all API calls to OpenVR, thereby insulating it from the user. If you're looking to get - * some information from the headset/controllers you should probably look at OpenVRStereoRenderer, ControllerListener, - * or OpenVRState - */ -public final class OpenVRProvider { - public static Texture_t[] texType = new Texture_t[2]; - - private static boolean initialized; - private static final Logger logger = LoggerFactory.getLogger(OpenVRProvider.class); - private static VR_IVRSystem_FnTable vrSystem; - private static VR_IVRCompositor_FnTable vrCompositor; - private static VR_IVROverlay_FnTable vrOverlay; - private static VR_IVRSettings_FnTable vrSettings; - private static int[] controllerDeviceIndex = new int[2]; - private static VRControllerState_t.ByReference[] inputStateRefernceArray = new VRControllerState_t.ByReference[2]; - private static VRControllerState_t[] controllerStateReference = new VRControllerState_t[2]; - private static IntBuffer hmdErrorStore; - private static TrackedDevicePose_t.ByReference hmdTrackedDevicePoseReference; - private static TrackedDevicePose_t[] hmdTrackedDevicePoses; - private static boolean[] controllerTracking = new boolean[2]; - - //keyboard - private static boolean keyboardShowing; - private static boolean headIsTracking; - private static OpenVRProvider instance; - - private static final OpenVRState VR_STATE = new OpenVRState(); - - // TextureIDs of framebuffers for each eye - private final VRTextureBounds_t texBounds = new VRTextureBounds_t(); - private float nearClip = 0.5f; - private float farClip = 500.0f; - - private OpenVRProvider() { - } - - // Get a singleton instance. - /** - * As a general rule, we should use this class as a singleton, because multiple instantiation - * will likely cause problems in the upstream native library. This provides a convenient method - * of using OpenVRProvider as a singleton. - */ - public static OpenVRProvider getInstance() { - if (instance == null) { - instance = new OpenVRProvider(); - } - return instance; - } - - /** - * Get the state of the VR system. This contains the poses of the eyes, controllers, etc... - * @return the VR state. - */ - public OpenVRState getState() { - return VR_STATE; - } - - /** - * Initialize the VR system. Note that calling this method will cause OpenVR to launch. If there is no headset - * connected, or if the OpenVR library fails to initialize for some reason, this will return false, and a log - * entry about why initialization failed will be written. - * @return true if successful. - */ - public boolean init() { - for (int handIndex = 0; handIndex < 2; handIndex++) { - controllerDeviceIndex[handIndex] = -1; - controllerStateReference[handIndex] = new VRControllerState_t(); - inputStateRefernceArray[handIndex] = new VRControllerState_t.ByReference(); - inputStateRefernceArray[handIndex].setAutoRead(false); - inputStateRefernceArray[handIndex].setAutoWrite(false); - inputStateRefernceArray[handIndex].setAutoSynch(false); - texType[handIndex] = new Texture_t(); - } - if (!initializeOpenVRLibrary()) { - logger.warn("JOpenVR library loading failed."); - return false; - } - if (!initializeJOpenVR()) { - logger.warn("JOpenVR initialization failed."); - return false; - } - int initAttempts = 0; - boolean initSuccess = false; - - // OpenVR has a race condition here - it is necessary - // to initialize the overlay, but certain operations - // that appear to take place outside of the main thread - // seem to prevent that from happening if it's done too - // soon after OpenVR is initialized. This loop waits a - // reasonable amount of time and makes several attempts. - // In my testing, it works all of the time. - while (!initSuccess && initAttempts < 10) { - try { - Thread.sleep(300); - } catch (InterruptedException ex) { - Thread.currentThread().interrupt(); - } - initSuccess = initOpenVRCompositor(true); - initAttempts++; - } - if (!initOpenVROverlay()) { - logger.warn("VROverlay initialization failed."); - return false; - } - if (!initOpenVROSettings()) { - logger.warn("OpenVR settings initialization failed."); - return false; - } - initialized = true; - return true; - } - - /** - * - * @return true if initialized. - */ - public boolean isInitialized() { - return initialized; - } - - /** - * In some instances, OpenVR will lose tracking on the head set. For example, if the line of sight to both light - * houses is obstructed, it is impossible to track the head set. In this case, the head set cannot be reliably - * tracked. In such cases, this method will return false, signaling that the head set tracking information returned - * by getEyePose() is unreliable. - * - * @return true if the pose of the headset is currently considered reliable. - */ - public boolean isHeadTracking() { - return headIsTracking; - } - - /** - * - * @param controllerIndex - 0 for left, 1 for right, an integer. - * @return true if the pose of the controller is currently considered reliable. - */ - public boolean isControllerTrackint(int controllerIndex) { - return controllerTracking[controllerIndex]; - } - - /** - * Shut down the VR system. - */ - public void shutdown() { - JOpenVRLibrary.VR_ShutdownInternal(); - vrSystem = null; - vrCompositor = null; - vrOverlay = null; - vrSettings = null; - initialized = false; - } - - /** - * Query the VR library and update the VR state, which can then be retrieved via getState(). - * This method should be called once per frame. - */ - public void updateState() { - updatePose(); - pollControllers(); - pollInputEvents(); - } - - /** - * Make the specified controller vibrate - * @param controller - the hand index, 0 for left and 1 for right, an integer. - * @param strength - the strength of the pulse - a short value from 0 - 3999. - */ - public static void triggerHapticPulse(int controller, int strength) { - if (controllerDeviceIndex[controller] == -1) { - return; - } - vrSystem.TriggerHapticPulse.apply(controllerDeviceIndex[controller], 0, (short) strength); - } - - /** - * Submit the frame stored in the frame buffers for the left and right eyes to the compositor. When this method is - * called, the contents of those frame buffers will show up in the head set. This method should be called exactly - * once per frame. - */ - public void submitFrame() { - for (int nEye = 0; nEye < 2; nEye++) { - vrCompositor.Submit.apply( - nEye, - texType[nEye], null, - JOpenVRLibrary.EVRSubmitFlags.EVRSubmitFlags_Submit_Default); - } - if (vrCompositor.PostPresentHandoff != null) { - vrCompositor.PostPresentHandoff.apply(); - } - } - - /** - * Set the distance of the camera from the near clipping plane, in OpenGL units, as a float. - * vrProvider.getState().getProjectionMatrix(...) method. - * @param nearClipIn - the near clip to set. - */ - public void setNearClip(float nearClipIn) { - this.nearClip = nearClipIn; - } - - /** - * Set the distance of the camera from the far clipping plane, in OpenGL units, as a float. - * vrProvider.getState().getProjectionMatrix(...) method. - * @param farClipIn - the near clip to set. - */ - public void setFarClip(float farClipIn) { - this.farClip = farClipIn; - } - - /** - * Turn on the keyboard overlay. This is a keyboard that hovers in front of the user, that can be typed upon by - * pointing the ray extending from the top of the controller at the key the user wants to press. - * @param showingState - true or false - * @return - true if successful. If this call fails, an error is logged. - */ - public static boolean setKeyboardOverlayShowing(boolean showingState) { - int ret; - if (showingState) { - Pointer pointer = new Memory(3); - pointer.setString(0, "mc"); - Pointer empty = new Memory(1); - empty.setString(0, ""); - ret = vrOverlay.ShowKeyboard.apply(0, 0, pointer, 256, empty, (byte) 1, 0); - keyboardShowing = 0 == ret; //0 = no error, > 0 see EVROverlayError - if (ret != 0) { - logger.error("VR Overlay Error: " + vrOverlay.GetOverlayErrorNameFromEnum.apply(ret).getString(0)); - } - } else { - try { - vrOverlay.HideKeyboard.apply(); - } catch (Error e) { - logger.error("Error bringing up keyboard overlay: " + e.toString()); - } - keyboardShowing = false; - } - - return keyboardShowing; - } - - private void pollControllers() { - for (int handIndex = 0; handIndex < 2; handIndex++) { - if (controllerDeviceIndex[handIndex] != -1) { - vrSystem.GetControllerState.apply(controllerDeviceIndex[handIndex], inputStateRefernceArray[handIndex]); - inputStateRefernceArray[handIndex].read(); - controllerStateReference[handIndex] = inputStateRefernceArray[handIndex]; - VR_STATE.updateControllerButtonState(controllerStateReference); - } - } - } - - private boolean initializeOpenVRLibrary() { - if (initialized) { - return true; - } - - String target = ""; - switch (OS.get()) { - case WINDOWS: - // Windows - target = "win32-" + (OS.IS_64 ? "x86-64" : "x86"); - break; - case MACOSX: - // osx - target = "darwin"; - break; - case LINUX: - // Assume Linux - target = "linux-" + (OS.IS_64 ? "x86-64" : "x86"); - break; - } - String path = Paths.get(OS.USER_DIRECTORY, "openvr_natives", target).toString(); - logger.info("Adding OpenVR search path: " + path); - NativeLibrary.addSearchPath("openvr_api", path); - - if (jopenvr.JOpenVRLibrary.VR_IsHmdPresent() != 1) { - logger.info("VR Headset not detected."); - return false; - } - logger.info("VR Headset detected."); - return true; - } - - private static boolean initializeJOpenVR() { - hmdErrorStore = IntBuffer.allocate(1); - vrSystem = null; - JOpenVRLibrary.VR_InitInternal(hmdErrorStore, JOpenVRLibrary.EVRApplicationType.EVRApplicationType_VRApplication_Scene); - if (hmdErrorStore.get(0) == 0) { - // ok, try and get the vrSystem pointer.. - vrSystem = new VR_IVRSystem_FnTable(JOpenVRLibrary.VR_GetGenericInterface(JOpenVRLibrary.IVRSystem_Version, hmdErrorStore)); - } - if (vrSystem == null || hmdErrorStore.get(0) != 0) { - String errorString = jopenvr.JOpenVRLibrary.VR_GetVRInitErrorAsEnglishDescription(hmdErrorStore.get(0)).getString(0); - logger.info("vrSystem initialization failed:" + errorString); - return false; - } else { - vrSystem.setAutoSynch(false); - vrSystem.read(); - - logger.info("OpenVR initialized & VR connected."); - - hmdTrackedDevicePoseReference = new TrackedDevicePose_t.ByReference(); - hmdTrackedDevicePoses = (TrackedDevicePose_t[]) hmdTrackedDevicePoseReference.toArray(JOpenVRLibrary.k_unMaxTrackedDeviceCount); - - // disable all this stuff which kills performance - hmdTrackedDevicePoseReference.setAutoRead(false); - hmdTrackedDevicePoseReference.setAutoWrite(false); - hmdTrackedDevicePoseReference.setAutoSynch(false); - for (int i = 0; i < JOpenVRLibrary.k_unMaxTrackedDeviceCount; i++) { - hmdTrackedDevicePoses[i].setAutoRead(false); - hmdTrackedDevicePoses[i].setAutoWrite(false); - hmdTrackedDevicePoses[i].setAutoSynch(false); - } - } - return true; - } - - // needed for in-game keyboard - private static boolean initOpenVROverlay() { - vrOverlay = new VR_IVROverlay_FnTable(JOpenVRLibrary.VR_GetGenericInterface(JOpenVRLibrary.IVROverlay_Version, hmdErrorStore)); - if (hmdErrorStore.get(0) == 0) { - vrOverlay.setAutoSynch(false); - vrOverlay.read(); - logger.info("OpenVR Overlay initialized OK."); - } else { - String errorString = jopenvr.JOpenVRLibrary.VR_GetVRInitErrorAsEnglishDescription(hmdErrorStore.get(0)).getString(0); - logger.info("vrOverlay initialization failed:" + errorString); - return false; - } - return true; - } - - private static boolean initOpenVROSettings() { - vrSettings = new VR_IVRSettings_FnTable(JOpenVRLibrary.VR_GetGenericInterface(JOpenVRLibrary.IVRSettings_Version, hmdErrorStore)); - if (hmdErrorStore.get(0) == 0) { - vrSettings.setAutoSynch(false); - vrSettings.read(); - logger.info("OpenVR Settings initialized OK."); - } else { - String errorString = jopenvr.JOpenVRLibrary.VR_GetVRInitErrorAsEnglishDescription(hmdErrorStore.get(0)).getString(0); - logger.info("OpenVROSettings initialization failed:" + errorString); - return false; - } - return true; - } - - private boolean initOpenVRCompositor(boolean set) { - if (set && vrSystem != null) { - vrCompositor = new VR_IVRCompositor_FnTable(JOpenVRLibrary.VR_GetGenericInterface(JOpenVRLibrary.IVRCompositor_Version, hmdErrorStore)); - if (hmdErrorStore.get(0) == 0) { - logger.info("OpenVR Compositor initialized OK."); - vrCompositor.setAutoSynch(false); - vrCompositor.read(); - vrCompositor.SetTrackingSpace.apply(JOpenVRLibrary.ETrackingUniverseOrigin.ETrackingUniverseOrigin_TrackingUniverseStanding); - } else { - String errorString = jopenvr.JOpenVRLibrary.VR_GetVRInitErrorAsEnglishDescription(hmdErrorStore.get(0)).getString(0); - logger.info("vrCompositor initialization failed:" + errorString); - return false; - } - } - if (vrCompositor == null) { - logger.info("Skipping VR Compositor..."); - } - - // left eye - texBounds.uMax = 1f; - texBounds.uMin = 0f; - texBounds.vMax = 1f; - texBounds.vMin = 0f; - texBounds.setAutoSynch(false); - texBounds.setAutoRead(false); - texBounds.setAutoWrite(false); - texBounds.write(); - // texture type - for (int nEye = 0; nEye < 2; nEye++) { - texType[0].eColorSpace = JOpenVRLibrary.EColorSpace.EColorSpace_ColorSpace_Gamma; - texType[0].eType = JOpenVRLibrary.EGraphicsAPIConvention.EGraphicsAPIConvention_API_OpenGL; - texType[0].setAutoSynch(false); - texType[0].setAutoRead(false); - texType[0].setAutoWrite(false); - texType[0].handle = -1; - texType[0].write(); - } - logger.info("OpenVR Compositor initialized OK."); - return true; - } - - private static void findControllerDevices() { - controllerDeviceIndex[ControllerListener.RIGHT_CONTROLLER] = -1; - controllerDeviceIndex[ControllerListener.LEFT_CONTROLLER] = -1; - - controllerDeviceIndex[ControllerListener.RIGHT_CONTROLLER] = - vrSystem.GetTrackedDeviceIndexForControllerRole.apply( - JOpenVRLibrary.ETrackedControllerRole.ETrackedControllerRole_TrackedControllerRole_LeftHand); - controllerDeviceIndex[ControllerListener.LEFT_CONTROLLER] = - vrSystem.GetTrackedDeviceIndexForControllerRole.apply( - JOpenVRLibrary.ETrackedControllerRole.ETrackedControllerRole_TrackedControllerRole_RightHand); - } - - private static void pollInputEvents() { - jopenvr.VREvent_t event = new jopenvr.VREvent_t(); - - while (vrSystem.PollNextEvent.apply(event, event.size()) > 0) { - - switch (event.eventType) { - case EVREventType.EVREventType_VREvent_KeyboardClosed: - //'huzzah' - keyboardShowing = false; - break; - case EVREventType.EVREventType_VREvent_KeyboardCharInput: - break; - default: - break; - } - } - } - - private void updatePose() { - vrCompositor.WaitGetPoses.apply(hmdTrackedDevicePoseReference, JOpenVRLibrary.k_unMaxTrackedDeviceCount, null, 0); - for (int nDevice = 0; nDevice < JOpenVRLibrary.k_unMaxTrackedDeviceCount; ++nDevice) { - hmdTrackedDevicePoses[nDevice].read(); - } - - if (hmdTrackedDevicePoses[JOpenVRLibrary.k_unTrackedDeviceIndex_Hmd].bPoseIsValid != 0) { - for (int nEye = 0; nEye < 2; nEye++) { - HmdMatrix34_t matPose = vrSystem.GetEyeToHeadTransform.apply(nEye); - VR_STATE.setEyePoseWRTHead(matPose, nEye); - HmdMatrix44_t matProjection = - vrSystem.GetProjectionMatrix.apply(nEye, - nearClip, - farClip, - JOpenVRLibrary.EGraphicsAPIConvention.EGraphicsAPIConvention_API_OpenGL); - VR_STATE.setProjectionMatrix(matProjection, nEye); - } - VR_STATE.setHeadPose(hmdTrackedDevicePoses[JOpenVRLibrary.k_unTrackedDeviceIndex_Hmd].mDeviceToAbsoluteTracking); - headIsTracking = true; - } else { - headIsTracking = false; - } - - findControllerDevices(); - - for (int handIndex = 0; handIndex < 2; handIndex++) { - if (controllerDeviceIndex[handIndex] != -1) { - controllerTracking[handIndex] = true; - VR_STATE.setControllerPose(hmdTrackedDevicePoses[controllerDeviceIndex[handIndex]].mDeviceToAbsoluteTracking, handIndex); - } else { - controllerTracking[handIndex] = false; - } - } - } -} diff --git a/engine/src/main/java/org/terasology/engine/rendering/openvrprovider/OpenVRState.java b/engine/src/main/java/org/terasology/engine/rendering/openvrprovider/OpenVRState.java deleted file mode 100644 index 269ef0ae8bc..00000000000 --- a/engine/src/main/java/org/terasology/engine/rendering/openvrprovider/OpenVRState.java +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright 2021 The Terasology Foundation -// SPDX-License-Identifier: Apache-2.0 -package org.terasology.engine.rendering.openvrprovider; - -import jopenvr.HmdMatrix34_t; -import jopenvr.HmdMatrix44_t; -import jopenvr.VRControllerAxis_t; -import jopenvr.VRControllerState_t; -import org.joml.Matrix4f; - -import java.util.ArrayList; -import java.util.List; - -/** Contains all of the information that the user will need from OpenVR without using any OpenVR data structures. The -OpenVRProvider automatically updates this. - */ -public class OpenVRState { - - // Controllers - private static Matrix4f[] controllerPose = new Matrix4f[2]; - private static VRControllerState_t[] lastControllerState = new VRControllerState_t[2]; - - private List controllerListeners = new ArrayList<>(); - - // In the head frame - private Matrix4f[] eyePoses = new Matrix4f[2]; - private Matrix4f[] projectionMatrices = new Matrix4f[2]; - private float groundPlaneYOffset; - - // In the tracking system intertial frame - private Matrix4f headPose = OpenVRUtil.createIdentityMatrix4f(); - - OpenVRState() { - for (int handIndex = 0; handIndex < 2; handIndex++) { - lastControllerState[handIndex] = new VRControllerState_t(); - controllerPose[handIndex] = OpenVRUtil.createIdentityMatrix4f(); - eyePoses[handIndex] = OpenVRUtil.createIdentityMatrix4f(); - projectionMatrices[handIndex] = OpenVRUtil.createIdentityMatrix4f(); - - for (int i = 0; i < 5; i++) { - lastControllerState[handIndex].rAxis[i] = new VRControllerAxis_t(); - } - } - } - - /** - * Add a controller listener. This listener will receive pose and button state updates for the controller. - * @param listener - An object implementing the ControllerListener interface. - */ - public void addControllerListener(ControllerListener listener) { - controllerListeners.add(listener); - } - - /** - * Removes a controller listener. - * @param listener - An object implementing the ControllerListener interface. - */ - public void removeControllerListener(ControllerListener listener) { - controllerListeners.remove(listener); - } - - /** - * Get the pose of an eye. - * @param eyeIndex - An integer specifying the eye: 0 for the left eye, 1 for the right eye. - * @return the pose, as a Matrix4f - */ - public Matrix4f getEyePose(int eyeIndex) { - Matrix4f matrixReturn = new Matrix4f(headPose); - matrixReturn.mul(eyePoses[eyeIndex]); - return matrixReturn; - } - - /** - * Get the projection matrix for an eye. - * @param eyeIndex - An integer specifying the eye: 0 for the left eye, 1 for the right eye. - * @return the projection matrix, as a Matrix4f. - */ - public Matrix4f getEyeProjectionMatrix(int eyeIndex) { - return new Matrix4f(projectionMatrices[eyeIndex]); - } - - void setHeadPose(HmdMatrix34_t inputPose) { - OpenVRUtil.setSteamVRMatrix3ToMatrix4f(inputPose, headPose); - headPose = new Matrix4f( - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, groundPlaneYOffset, 0, 1 - ).mul(headPose); - } - - void setEyePoseWRTHead(HmdMatrix34_t inputPose, int nIndex) { - OpenVRUtil.setSteamVRMatrix3ToMatrix4f(inputPose, eyePoses[nIndex]); - } - - void setControllerPose(HmdMatrix34_t inputPose, int nIndex) { - OpenVRUtil.setSteamVRMatrix3ToMatrix4f(inputPose, controllerPose[nIndex]); - controllerPose[nIndex] = new Matrix4f( - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, groundPlaneYOffset, 0, 1 - ).mul(controllerPose[nIndex]); - for (ControllerListener listener : controllerListeners) { - listener.poseChanged(controllerPose[nIndex], nIndex); - } - } - - void updateControllerButtonState( - VRControllerState_t[] controllerStateReference) { - // each controller{ - for (int handIndex = 0; handIndex < 2; handIndex++) { - // store previous state - if (lastControllerState[handIndex].ulButtonPressed != controllerStateReference[handIndex].ulButtonPressed) { - for (ControllerListener listener : controllerListeners) { - listener.buttonStateChanged(lastControllerState[handIndex], controllerStateReference[handIndex], handIndex); - } - } - lastControllerState[handIndex].unPacketNum = controllerStateReference[handIndex].unPacketNum; - lastControllerState[handIndex].ulButtonPressed = controllerStateReference[handIndex].ulButtonPressed; - lastControllerState[handIndex].ulButtonTouched = controllerStateReference[handIndex].ulButtonTouched; - - // 5 axes but only [0] and [1] is anything, trigger and touchpad - for (int i = 0; i < 5; i++) { - if (controllerStateReference[handIndex].rAxis[i] != null) { - lastControllerState[handIndex].rAxis[i].x = controllerStateReference[handIndex].rAxis[i].x; - lastControllerState[handIndex].rAxis[i].y = controllerStateReference[handIndex].rAxis[i].y; - } - } - } - } - - /** - * Set the offset of the default head pose from the ground (along the y axis). This is useful if there is some - * built-in "rest" camera position (i.e. some games will default to the height of a standing player). - * @param inputOffset - the height (in meters) by which to raise the camera. - */ - public void setGroundPlaneYOffset(float inputOffset) { - groundPlaneYOffset = inputOffset; - } - - void setProjectionMatrix( - HmdMatrix44_t inputPose, - int eyeIndex) { - OpenVRUtil.setSteamVRMatrix44ToMatrix4f(inputPose, projectionMatrices[eyeIndex]); - } - -} diff --git a/engine/src/main/java/org/terasology/engine/rendering/openvrprovider/OpenVRUtil.java b/engine/src/main/java/org/terasology/engine/rendering/openvrprovider/OpenVRUtil.java deleted file mode 100644 index fd1bbd6a8a7..00000000000 --- a/engine/src/main/java/org/terasology/engine/rendering/openvrprovider/OpenVRUtil.java +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright 2021 The Terasology Foundation -// SPDX-License-Identifier: Apache-2.0 -package org.terasology.engine.rendering.openvrprovider; - -import jopenvr.HmdMatrix34_t; -import jopenvr.HmdMatrix44_t; -import jopenvr.VRControllerState_t; -import org.joml.Matrix4f; -import org.joml.Vector3f; -import org.joml.Vector4f; - -/** - * Utility functions that don't interact with the headset (conversions and the like). - */ -public final class OpenVRUtil { - - private OpenVRUtil() { - // Not called - } - - static void setSteamVRMatrix3ToMatrix4f(HmdMatrix34_t hmdMatrix, Matrix4f matrixToSet) { - matrixToSet.set( - hmdMatrix.m[0], hmdMatrix.m[4], hmdMatrix.m[8], 0, - hmdMatrix.m[1], hmdMatrix.m[5], hmdMatrix.m[9], 0, - hmdMatrix.m[2], hmdMatrix.m[6], hmdMatrix.m[10], 0, - hmdMatrix.m[3], hmdMatrix.m[7], hmdMatrix.m[11], 1f - ); - } - - static void setSteamVRMatrix44ToMatrix4f(HmdMatrix44_t hmdMatrix, Matrix4f matrixToSet) { - matrixToSet.set( - hmdMatrix.m[0], hmdMatrix.m[4], hmdMatrix.m[8], hmdMatrix.m[12], - hmdMatrix.m[1], hmdMatrix.m[5], hmdMatrix.m[9], hmdMatrix.m[13], - hmdMatrix.m[2], hmdMatrix.m[6], hmdMatrix.m[10], hmdMatrix.m[14], - hmdMatrix.m[3], hmdMatrix.m[7], hmdMatrix.m[11], hmdMatrix.m[15] - ); - } - - public static VRControllerState_t createZeroControllerState() { - VRControllerState_t state = new VRControllerState_t(); - // controller not connected, clear state - state.ulButtonPressed = 0; - - for (int i = 0; i < 5; i++) { - if (state.rAxis[i] != null) { - state.rAxis[i].x = 0.0f; - state.rAxis[i].y = 0.0f; - } - } - return state; - } - - public static boolean isPressed(long nButton, long uiButtonPressed) { - return ((uiButtonPressed & nButton) > 0); - } - - public static boolean switchedDown(long nButton, long stateBefore, long stateAfter) { - return (!isPressed(nButton, stateBefore) && isPressed(nButton, stateAfter)); - } - - public static boolean switchedUp(long nButton, long stateBefore, long stateAfter) { - return (isPressed(nButton, stateBefore) && !isPressed(nButton, stateAfter)); - } - - /* - * Takes 3 unit vectors, representing an orthonormal basis, and generates a unit quaternion. - */ - public static Vector4f getQuaternion(boolean normalizeAxes, - float xxInput, float xyInput, float xzInput, - float yxInput, float yyInput, float yzInput, - float zxInput, float zyInput, float zzInput) { - float xx = xxInput; - float xy = xyInput; - float xz = xzInput; - float yx = yxInput; - float yy = yyInput; - float yz = yzInput; - float zx = zxInput; - float zy = zyInput; - float zz = zzInput; - float x; - float y; - float z; - float w; - if (normalizeAxes) { - final float lx = 1f / new Vector3f(xx, xy, xz).length(); - final float ly = 1f / new Vector3f(yx, yy, yz).length(); - final float lz = 1f / new Vector3f(zx, zy, zz).length(); - xx *= lx; - xy *= lx; - xz *= lx; - yx *= ly; - yy *= ly; - yz *= ly; - zx *= lz; - zy *= lz; - zz *= lz; - } - // the trace is the sum of the diagonal elements; see - // http://mathworld.wolfram.com/MatrixTrace.html - final float t = xx + yy + zz; - - // we protect the division by s by ensuring that s>=1 - if (t >= 0) { // |w| >= .5 - float s = (float) Math.sqrt(t + 1); // |s|>=1 ... - w = 0.5f * s; - s = 0.5f / s; // so this division isn't bad - x = (zy - yz) * s; - y = (xz - zx) * s; - z = (yx - xy) * s; - } else if ((xx > yy) && (xx > zz)) { - float s = (float) Math.sqrt(1.0 + xx - yy - zz); // |s|>=1 - x = s * 0.5f; // |x| >= .5 - s = 0.5f / s; - y = (yx + xy) * s; - z = (xz + zx) * s; - w = (zy - yz) * s; - } else if (yy > zz) { - float s = (float) Math.sqrt(1.0 + yy - xx - zz); // |s|>=1 - y = s * 0.5f; // |y| >= .5 - s = 0.5f / s; - x = (yx + xy) * s; - z = (zy + yz) * s; - w = (xz - zx) * s; - } else { - float s = (float) Math.sqrt(1.0 + zz - xx - yy); // |s|>=1 - z = s * 0.5f; // |z| >= .5 - s = 0.5f / s; - x = (xz + zx) * s; - y = (zy + yz) * s; - w = (yx - xy) * s; - } - return new Vector4f(x, y, z, w); - } - - /* - * Converts the rotation portion of a 4x4 matrix into a unit quaternion. - */ - public static Vector4f convertToQuaternion(Matrix4f m1) { - return getQuaternion(true, - m1.m00(), m1.m10(), m1.m20(), - m1.m01(), m1.m11(), m1.m21(), - m1.m02(), m1.m12(), m1.m22() - ); - } - - static Matrix4f createIdentityMatrix4f() { - return new Matrix4f(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); - } -} diff --git a/engine/src/main/java/org/terasology/engine/rendering/openvrprovider/package-info.java b/engine/src/main/java/org/terasology/engine/rendering/openvrprovider/package-info.java deleted file mode 100644 index ef8dfad121a..00000000000 --- a/engine/src/main/java/org/terasology/engine/rendering/openvrprovider/package-info.java +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright 2021 The Terasology Foundation -// SPDX-License-Identifier: Apache-2.0 -@API -package org.terasology.engine.rendering.openvrprovider; - -import org.terasology.gestalt.module.sandbox.API; diff --git a/engine/src/main/java/org/terasology/engine/rendering/world/WorldRendererImpl.java b/engine/src/main/java/org/terasology/engine/rendering/world/WorldRendererImpl.java index cfe1713bfe5..9c31f081178 100644 --- a/engine/src/main/java/org/terasology/engine/rendering/world/WorldRendererImpl.java +++ b/engine/src/main/java/org/terasology/engine/rendering/world/WorldRendererImpl.java @@ -7,7 +7,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.terasology.engine.config.Config; -import org.terasology.engine.config.PlayerConfig; import org.terasology.engine.config.RenderingConfig; import org.terasology.engine.context.Context; import org.terasology.engine.core.GameEngine; @@ -26,7 +25,6 @@ import org.terasology.engine.rendering.assets.material.Material; import org.terasology.engine.rendering.backdrop.BackdropProvider; import org.terasology.engine.rendering.cameras.Camera; -import org.terasology.engine.rendering.cameras.OpenVRStereoCamera; import org.terasology.engine.rendering.cameras.PerspectiveCamera; import org.terasology.engine.rendering.dag.ModuleRendering; import org.terasology.engine.rendering.dag.Node; @@ -37,7 +35,6 @@ import org.terasology.engine.rendering.opengl.FBO; import org.terasology.engine.rendering.opengl.ScreenGrabber; import org.terasology.engine.rendering.opengl.fbms.DisplayResolutionDependentFbo; -import org.terasology.engine.rendering.openvrprovider.OpenVRProvider; import org.terasology.engine.rendering.primitives.ChunkTessellator; import org.terasology.engine.rendering.world.viewDistance.ViewDistance; import org.terasology.engine.utilities.Assets; @@ -61,8 +58,6 @@ * Renders the 3D world, including background, overlays and first person/in hand objects. 2D UI elements are dealt with * elsewhere. *

- * This implementation includes support for OpenVR, through which HTC Vive and Oculus Rift is supported. - *

* This implementation works closely with a number of support objects, in particular: *

* TODO: update this section to include new, relevant objects - a RenderableWorld instance, providing acceleration @@ -87,8 +82,6 @@ public final class WorldRendererImpl implements WorldRenderer { private final ShaderManager shaderManager; private final Camera playerCamera; - private final OpenVRProvider vrProvider; - private float timeSmoothedMainLightIntensity; private RenderingStage currentRenderingStage; @@ -130,32 +123,8 @@ public WorldRendererImpl(Context context) { this.backdropProvider = context.get(BackdropProvider.class); this.renderingConfig = context.get(Config.class).getRendering(); this.shaderManager = context.get(ShaderManager.class); - // TODO: Instantiate the VR provider at a more reasonable location, and just obtain it via context here. - vrProvider = OpenVRProvider.getInstance(); - if (renderingConfig.isVrSupport()) { - context.put(OpenVRProvider.class, vrProvider); - // If vrProvider.init() returns false, this means that we are unable to initialize VR hardware for some - // reason (for example, no HMD is connected). In that case, even though the configuration requests - // vrSupport, we fall back on rendering to the main display. The reason for init failure can be read from - // the log. - if (vrProvider.init()) { - playerCamera = new OpenVRStereoCamera(vrProvider); - /* - * The origin of OpenVR's coordinate system lies on the ground of the user. We have to move this origin - * such that the ground plane of the rendering system and the ground plane of the room the VR user is - * in match. - */ - vrProvider.getState().setGroundPlaneYOffset( - GROUND_PLANE_HEIGHT_DISPARITY - context.get(PlayerConfig.class).eyeHeight.get()); - currentRenderingStage = RenderingStage.LEFT_EYE; - } else { - playerCamera = new PerspectiveCamera(renderingConfig, context.get(DisplayDevice.class)); - currentRenderingStage = RenderingStage.MONO; - } - } else { - playerCamera = new PerspectiveCamera(renderingConfig, context.get(DisplayDevice.class)); - currentRenderingStage = RenderingStage.MONO; - } + playerCamera = new PerspectiveCamera(renderingConfig, context.get(DisplayDevice.class)); + currentRenderingStage = RenderingStage.MONO; // TODO: won't need localPlayerSystem here once camera is in the ES proper LocalPlayerSystem localPlayerSystem = context.get(LocalPlayerSystem.class); localPlayerSystem.setPlayerCamera(playerCamera); diff --git a/engine/src/main/resources/org/terasology/engine/assets/ui/menu/videoMenuScreen.ui b/engine/src/main/resources/org/terasology/engine/assets/ui/menu/videoMenuScreen.ui index c3669fed969..381bc05cd7d 100644 --- a/engine/src/main/resources/org/terasology/engine/assets/ui/menu/videoMenuScreen.ui +++ b/engine/src/main/resources/org/terasology/engine/assets/ui/menu/videoMenuScreen.ui @@ -361,16 +361,6 @@ "type": "UIDropdown", "id": "shadows" }, - { - "type": "UILabel", - "text": "${engine:menu#oculus-rift-support}: ", - "enabled": false - }, - { - "type": "UICheckbox", - "id": "oculusVrSupport", - "enabled": false - }, { "type": "UILabel", "text": "${engine:menu#ssao}: " diff --git a/facades/PC/src/main/java/org/terasology/engine/Terasology.java b/facades/PC/src/main/java/org/terasology/engine/Terasology.java index 776325c00bd..433380f9f00 100644 --- a/facades/PC/src/main/java/org/terasology/engine/Terasology.java +++ b/facades/PC/src/main/java/org/terasology/engine/Terasology.java @@ -31,7 +31,6 @@ import org.terasology.engine.core.subsystem.lwjgl.LwjglGraphics; import org.terasology.engine.core.subsystem.lwjgl.LwjglInput; import org.terasology.engine.core.subsystem.lwjgl.LwjglTimer; -import org.terasology.engine.core.subsystem.openvr.OpenVRInput; import org.terasology.engine.game.GameManifest; import org.terasology.engine.network.NetworkMode; import org.terasology.engine.rendering.nui.layers.mainMenu.savedGames.GameInfo; @@ -313,8 +312,7 @@ private void populateSubsystems(TerasologyEngineBuilder builder) { .add(new LwjglGraphics()) .add(new LwjglTimer()) .add(new LwjglInput()) - .add(new BindsSubsystem()) - .add(new OpenVRInput()); + .add(new BindsSubsystem()); builder.add(new DiscordRPCSubSystem()); } builder.add(new HibernationSubsystem()); From 9a9bd1931ef5aa6f65546a4f97cd388b63dda388 Mon Sep 17 00:00:00 2001 From: Tobias Nett Date: Thu, 13 Jul 2023 21:12:36 +0200 Subject: [PATCH 020/100] refactor!: remove TeraEd facade (#5115) * git rm -r facades/TeraEd * remove remaining references to TeraEd TeraEd is a facade wrapping the game itself into a desktop application to provide additional tools "around" it. For instance, it was used to tweak shader parameters while the game was running. Ideas for extension of this facade included monitoring capabilities, for instance, inspecting the state of the ECS, but were never implemented. The benefit of having this outer layer is that we could augment the game itself with additional developer information without baking those into the game itself. TeraEd used Swing and AWT for its UI. The TeraEd facade was unused for a long time and has fallen victim to bit rot. The logic handling state changes to shader parameters was commented out 6 years ago in b10b0f6. --- .gitignore | 1 - .../TeraEd__rendering_editor_.xml | 27 -- config/groovy/facade.groovy | 2 +- facades/TeraEd/.gitignore | 11 - facades/TeraEd/build.gradle | 94 ------- .../java/org/terasology/editor/TeraEd.java | 118 -------- .../editor/input/AwtKeyboardDevice.java | 264 ------------------ .../editor/input/AwtMouseDevice.java | 164 ----------- .../editor/properties/FloatProperty.java | 58 ---- .../editor/properties/Property.java | 14 - .../editor/properties/PropertyProvider.java | 14 - .../editor/properties/ReflectionProvider.java | 53 ---- .../editor/properties/SceneProperties.java | 45 --- .../terasology/editor/subsystem/AwtInput.java | 60 ---- .../editor/subsystem/LwjglPortlet.java | 219 --------------- .../subsystem/LwjglPortletDisplayDevice.java | 130 --------- .../org/terasology/editor/ui/MainWindow.java | 183 ------------ .../terasology/editor/ui/PropertyPanel.java | 72 ----- .../terasology/editor/ui/PropertySlider.java | 77 ----- .../org/terasology/editor/ui/Viewport.java | 39 --- facades/TeraEd/src/main/resources/logback.xml | 26 -- 21 files changed, 1 insertion(+), 1670 deletions(-) delete mode 100644 .idea/runConfigurations/TeraEd__rendering_editor_.xml delete mode 100644 facades/TeraEd/.gitignore delete mode 100644 facades/TeraEd/build.gradle delete mode 100644 facades/TeraEd/src/main/java/org/terasology/editor/TeraEd.java delete mode 100644 facades/TeraEd/src/main/java/org/terasology/editor/input/AwtKeyboardDevice.java delete mode 100644 facades/TeraEd/src/main/java/org/terasology/editor/input/AwtMouseDevice.java delete mode 100644 facades/TeraEd/src/main/java/org/terasology/editor/properties/FloatProperty.java delete mode 100644 facades/TeraEd/src/main/java/org/terasology/editor/properties/Property.java delete mode 100644 facades/TeraEd/src/main/java/org/terasology/editor/properties/PropertyProvider.java delete mode 100644 facades/TeraEd/src/main/java/org/terasology/editor/properties/ReflectionProvider.java delete mode 100644 facades/TeraEd/src/main/java/org/terasology/editor/properties/SceneProperties.java delete mode 100644 facades/TeraEd/src/main/java/org/terasology/editor/subsystem/AwtInput.java delete mode 100644 facades/TeraEd/src/main/java/org/terasology/editor/subsystem/LwjglPortlet.java delete mode 100644 facades/TeraEd/src/main/java/org/terasology/editor/subsystem/LwjglPortletDisplayDevice.java delete mode 100644 facades/TeraEd/src/main/java/org/terasology/editor/ui/MainWindow.java delete mode 100644 facades/TeraEd/src/main/java/org/terasology/editor/ui/PropertyPanel.java delete mode 100644 facades/TeraEd/src/main/java/org/terasology/editor/ui/PropertySlider.java delete mode 100644 facades/TeraEd/src/main/java/org/terasology/editor/ui/Viewport.java delete mode 100644 facades/TeraEd/src/main/resources/logback.xml diff --git a/.gitignore b/.gitignore index c1af677b996..8114c1e13ee 100644 --- a/.gitignore +++ b/.gitignore @@ -16,7 +16,6 @@ extensions /facades/* !/facades/PC -!/facades/TeraEd !/facades/subprojects.gradle /modules/* !/modules/subprojects.gradle diff --git a/.idea/runConfigurations/TeraEd__rendering_editor_.xml b/.idea/runConfigurations/TeraEd__rendering_editor_.xml deleted file mode 100644 index 6006938c49b..00000000000 --- a/.idea/runConfigurations/TeraEd__rendering_editor_.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - \ No newline at end of file diff --git a/config/groovy/facade.groovy b/config/groovy/facade.groovy index 78915196ef1..58b909d29a9 100644 --- a/config/groovy/facade.groovy +++ b/config/groovy/facade.groovy @@ -1,7 +1,7 @@ class facade { - def excludedItems = ["PC", "TeraEd"] + def excludedItems = ["PC"] def getGithubDefaultHome(Properties properties) { return properties.alternativeGithubHome ?: "MovingBlocks" diff --git a/facades/TeraEd/.gitignore b/facades/TeraEd/.gitignore deleted file mode 100644 index 7263b6ee479..00000000000 --- a/facades/TeraEd/.gitignore +++ /dev/null @@ -1,11 +0,0 @@ -# Boo eclipse! - every eclipse project needs its own .gitignore file - -# Ignore Eclipse -/.checkstyle -/.project -/.classpath -/.settings/ -/bin/ - -# Ignore gradle -/build/ diff --git a/facades/TeraEd/build.gradle b/facades/TeraEd/build.gradle deleted file mode 100644 index 4b1f24b7228..00000000000 --- a/facades/TeraEd/build.gradle +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2021 The Terasology Foundation -// SPDX-License-Identifier: Apache-2.0 - -// The Editor facade is responsible for the (shader) editor - a plain Java application runnable on PCs - -plugins { - id "application" -} - -// Grab all the common stuff like plugins to use, artifact repositories, code analysis config -apply from: "$rootDir/config/gradle/publish.gradle" - -// Base the engine tests on the same version number as the engine -version = project(':engine').version -println "TeraEd VERSION: $version" - -// Jenkins-Artifactory integration catches on to this as part of the Maven-type descriptor -group = 'org.terasology.facades' - -sourceSets { - // Adjust output path (changed with the Gradle 6 upgrade, this puts it back) - main.java.outputDir = new File("$buildDir/classes") - test.java.outputDir = new File("$buildDir/testClasses") -} - -dependencies { - implementation project(':engine') - implementation "org.terasology:reflections:0.9.12-MB" - - runtimeOnly(platform(project(":modules"))) - // For the "natives" configuration make it depend on the native files from LWJGL - implementation platform("org.lwjgl:lwjgl-bom:$LwjglVersion") - ["natives-linux", "natives-windows", "natives-macos"].forEach { - implementation "org.lwjgl:lwjgl::$it" - implementation "org.lwjgl:lwjgl-assimp::$it" - implementation "org.lwjgl:lwjgl-glfw::$it" - implementation "org.lwjgl:lwjgl-openal::$it" - implementation "org.lwjgl:lwjgl-opengl::$it" - implementation "org.lwjgl:lwjgl-stb::$it" - } - implementation "org.lwjgl:lwjgl-jawt" - - implementation(group: 'com.google.guava', name: 'guava', version: '30.1-jre') - - implementation(project(":subsystems:DiscordRPC")) - implementation(project(":subsystems:TypeHandlerLibrary")) - - implementation(group: 'org.lwjglx', name: 'lwjgl3-awt', version: '0.1.7') { - exclude group: 'org.lwjgl', module: '' - } -} - -application { - mainClass = "org.terasology.editor.TeraEd" -} - -run { - description = "Run 'TeraEd' to configure graphics shader parameters in a standard PC application" - group = "terasology run" - - workingDir = rootDir - args "-homedir" -} - -task editor(type: JavaExec) { - description = "Run 'TeraEd' to configure graphics shader parameters in a standard PC application" - group = "terasology run" - - // Dependencies: natives + all modules & the PC facade itself (which will trigger the engine) - dependsOn rootProject.extractNatives - dependsOn classes - - // Run arguments - main = 'org.terasology.editor.TeraEd' - workingDir = rootDir - String[] runArgs = ["-homedir"] - args runArgs - - // Classpath: PC itself, engine classes, engine dependencies. Not modules or natives since the engine finds those - classpath sourceSets.main.output.classesDirs - classpath sourceSets.main.output.resourcesDir - classpath project(':engine').sourceSets.main.output.classesDirs - classpath project(':engine').configurations.runtimeClasspath -} - -// Prep an IntelliJ module for the facade -idea { - module { - // Change around the output a bit - inheritOutputDirs = false - outputDir = file('build/classes') - testOutputDir = file('build/testClasses') - } -} diff --git a/facades/TeraEd/src/main/java/org/terasology/editor/TeraEd.java b/facades/TeraEd/src/main/java/org/terasology/editor/TeraEd.java deleted file mode 100644 index 140ab5f541f..00000000000 --- a/facades/TeraEd/src/main/java/org/terasology/editor/TeraEd.java +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright 2021 The Terasology Foundation -// SPDX-License-Identifier: Apache-2.0 - -package org.terasology.editor; - -import org.lwjgl.glfw.GLFW; -import org.lwjgl.opengl.awt.AWTGLCanvas; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.terasology.editor.properties.SceneProperties; -import org.terasology.editor.subsystem.AwtInput; -import org.terasology.editor.subsystem.LwjglPortlet; -import org.terasology.editor.ui.MainWindow; -import org.terasology.engine.core.GameEngine; -import org.terasology.engine.core.PathManager; -import org.terasology.engine.core.TerasologyEngine; -import org.terasology.engine.core.TerasologyEngineBuilder; -import org.terasology.engine.core.modes.StateMainMenu; -import org.terasology.engine.core.subsystem.config.BindsSubsystem; -import org.terasology.engine.core.subsystem.lwjgl.LwjglAudio; -import org.terasology.engine.core.subsystem.lwjgl.LwjglTimer; -import org.terasology.engine.monitoring.PerformanceMonitor; - -import javax.swing.JPopupMenu; -import javax.swing.JWindow; -import javax.swing.SwingUtilities; -import javax.swing.UIManager; - -/** - * TeraEd main class. - */ -@SuppressWarnings("serial") -public final class TeraEd extends JWindow { - - private MainWindow mainWindow; - private TerasologyEngine engine; - private final Logger logger = LoggerFactory.getLogger(TeraEd.class); - - private SceneProperties sceneProperties; - - public static void main(String[] args) { - new TeraEd().run(); - } - - public void run() { - JPopupMenu.setDefaultLightWeightPopupEnabled(false); - - try { - for (UIManager.LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) { - if ("Nimbus".equals(info.getName())) { - UIManager.setLookAndFeel(info.getClassName()); - break; - } - } - } catch (Exception e) { - // If Nimbus is not available, you can set the GUI to another look and feel. - logger.warn("Failed to set look and feel to Nimbus", e); - } - - try { - LwjglPortlet portlet = new LwjglPortlet(); - - PathManager.getInstance().useDefaultHomePath(); - - engine = new TerasologyEngineBuilder() - .add(new LwjglTimer()) - .add(new LwjglAudio()) - .add(new AwtInput()) - .add(new BindsSubsystem()) - .add(portlet).build(); - - if (!GLFW.glfwInit()) { - throw new RuntimeException("Failed to initialize GLFW"); - } - sceneProperties = new SceneProperties(engine); - - mainWindow = new MainWindow(this, engine); - portlet.createCanvas(); - AWTGLCanvas canvas = portlet.getCanvas(); - - engine.subscribeToStateChange(mainWindow); - engine.initializeRun(new StateMainMenu()); - - mainWindow.getViewport().setTerasology(canvas); - - portlet.initInputs(); - - Runnable renderLoop = new Runnable() { - public void run() { - if (canvas.isValid()) { - canvas.render(); - } - SwingUtilities.invokeLater(this); - } - }; - - // Setup swing thread as game thread - PerformanceMonitor.startActivity("Other"); - SwingUtilities.invokeAndWait(portlet::setupThreads); - SwingUtilities.invokeLater(renderLoop); - PerformanceMonitor.endActivity(); - } catch (Throwable t) { - logger.error("Uncaught Exception", t); - } - } - - public GameEngine getEngine() { - return engine; - } - - public MainWindow getMainWindow() { - return mainWindow; - } - - public SceneProperties getSceneProperties() { - return sceneProperties; - } -} diff --git a/facades/TeraEd/src/main/java/org/terasology/editor/input/AwtKeyboardDevice.java b/facades/TeraEd/src/main/java/org/terasology/editor/input/AwtKeyboardDevice.java deleted file mode 100644 index d3df9658ce3..00000000000 --- a/facades/TeraEd/src/main/java/org/terasology/editor/input/AwtKeyboardDevice.java +++ /dev/null @@ -1,264 +0,0 @@ -// Copyright 2021 The Terasology Foundation -// SPDX-License-Identifier: Apache-2.0 - -package org.terasology.editor.input; - -import com.google.common.collect.Lists; -import gnu.trove.map.TIntIntMap; -import gnu.trove.map.TIntObjectMap; -import gnu.trove.map.hash.TIntIntHashMap; -import gnu.trove.map.hash.TIntObjectHashMap; -import gnu.trove.set.TIntSet; -import gnu.trove.set.hash.TIntHashSet; -import org.lwjgl.opengl.awt.AWTGLCanvas; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.terasology.input.ButtonState; -import org.terasology.input.Input; -import org.terasology.input.InputType; -import org.terasology.input.Keyboard; -import org.terasology.input.device.CharKeyboardAction; -import org.terasology.input.device.KeyboardDevice; -import org.terasology.input.device.RawKeyboardAction; - -import java.awt.event.KeyEvent; -import java.awt.event.KeyListener; -import java.util.Queue; - -/** - * AWT converting keyboard device representation. - */ -public class AwtKeyboardDevice implements KeyboardDevice { - - private static final Logger logger = LoggerFactory.getLogger(AwtKeyboardDevice.class); - private static final TIntIntMap AWT_TO_TERA_MAPPING = new TIntIntHashMap(); - private static final TIntObjectMap AWT_TO_TERA_EXTRA = new TIntObjectHashMap<>(); - - static { - //TODO: test and cleanup keys -// AWT_TO_TERA_MAPPING.put(KeyEvent.VK_NONE, Keyboard.KeyId.NONE); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_ESCAPE, Keyboard.KeyId.ESCAPE); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_1, Keyboard.KeyId.KEY_1); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_2, Keyboard.KeyId.KEY_2); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_3, Keyboard.KeyId.KEY_3); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_4, Keyboard.KeyId.KEY_4); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_5, Keyboard.KeyId.KEY_5); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_6, Keyboard.KeyId.KEY_6); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_7, Keyboard.KeyId.KEY_7); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_8, Keyboard.KeyId.KEY_8); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_9, Keyboard.KeyId.KEY_9); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_0, Keyboard.KeyId.KEY_0); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_MINUS, Keyboard.KeyId.MINUS); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_BACK_SPACE, Keyboard.KeyId.BACKSPACE); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_TAB, Keyboard.KeyId.TAB); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_Q, Keyboard.KeyId.Q); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_W, Keyboard.KeyId.W); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_E, Keyboard.KeyId.E); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_R, Keyboard.KeyId.R); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_T, Keyboard.KeyId.T); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_Y, Keyboard.KeyId.Y); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_U, Keyboard.KeyId.U); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_I, Keyboard.KeyId.I); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_O, Keyboard.KeyId.O); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_P, Keyboard.KeyId.P); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_OPEN_BRACKET, Keyboard.KeyId.LEFT_BRACKET); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_CLOSE_BRACKET, Keyboard.KeyId.RIGHT_BRACKET); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_A, Keyboard.KeyId.A); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_S, Keyboard.KeyId.S); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_D, Keyboard.KeyId.D); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_F, Keyboard.KeyId.F); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_G, Keyboard.KeyId.G); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_H, Keyboard.KeyId.H); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_J, Keyboard.KeyId.J); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_K, Keyboard.KeyId.K); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_L, Keyboard.KeyId.L); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_SEMICOLON, Keyboard.KeyId.SEMICOLON); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_DEAD_ACUTE, Keyboard.KeyId.APOSTROPHE); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_DEAD_GRAVE, Keyboard.KeyId.GRAVE); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_BACK_SLASH, Keyboard.KeyId.BACKSLASH); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_Z, Keyboard.KeyId.Z); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_X, Keyboard.KeyId.X); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_C, Keyboard.KeyId.C); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_V, Keyboard.KeyId.V); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_B, Keyboard.KeyId.B); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_N, Keyboard.KeyId.N); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_M, Keyboard.KeyId.M); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_COMMA, Keyboard.KeyId.COMMA); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_PERIOD, Keyboard.KeyId.PERIOD); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_SLASH, Keyboard.KeyId.SLASH); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_MULTIPLY, Keyboard.KeyId.NUMPAD_MULTIPLY); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_SPACE, Keyboard.KeyId.SPACE); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_CAPS_LOCK, Keyboard.KeyId.CAPS_LOCK); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_F1, Keyboard.KeyId.F1); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_F2, Keyboard.KeyId.F2); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_F3, Keyboard.KeyId.F3); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_F4, Keyboard.KeyId.F4); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_F5, Keyboard.KeyId.F5); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_F6, Keyboard.KeyId.F6); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_F7, Keyboard.KeyId.F7); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_F8, Keyboard.KeyId.F8); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_F9, Keyboard.KeyId.F9); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_F10, Keyboard.KeyId.F10); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_NUM_LOCK, Keyboard.KeyId.NUM_LOCK); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_SCROLL_LOCK, Keyboard.KeyId.SCROLL_LOCK); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_NUMPAD7, Keyboard.KeyId.NUMPAD_7); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_NUMPAD8, Keyboard.KeyId.NUMPAD_8); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_NUMPAD9, Keyboard.KeyId.NUMPAD_9); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_SUBTRACT, Keyboard.KeyId.NUMPAD_MINUS); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_NUMPAD4, Keyboard.KeyId.NUMPAD_4); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_NUMPAD5, Keyboard.KeyId.NUMPAD_5); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_NUMPAD6, Keyboard.KeyId.NUMPAD_6); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_ADD, Keyboard.KeyId.NUMPAD_PLUS); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_NUMPAD1, Keyboard.KeyId.NUMPAD_1); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_NUMPAD2, Keyboard.KeyId.NUMPAD_2); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_NUMPAD3, Keyboard.KeyId.NUMPAD_3); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_NUMPAD0, Keyboard.KeyId.NUMPAD_0); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_DECIMAL, Keyboard.KeyId.NUMPAD_PERIOD); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_F11, Keyboard.KeyId.F11); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_F12, Keyboard.KeyId.F12); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_F13, Keyboard.KeyId.F13); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_F14, Keyboard.KeyId.F14); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_F15, Keyboard.KeyId.F15); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_F16, Keyboard.KeyId.F16); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_F17, Keyboard.KeyId.F17); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_F18, Keyboard.KeyId.F18); -// glfwToTeraMaps.put(KeyEvent.VK_KANA, Keyboard.KeyId.KANA); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_F19, Keyboard.KeyId.F19); -// glfwToTeraMaps.put(KeyEvent.VK_CONVERT, Keyboard.KeyId.CONVERT); -// glfwToTeraMaps.put(KeyEvent.VK_NOCONVERT, Keyboard.KeyId.NOCONVERT); -// glfwToTeraMaps.put(KeyEvent.VK_YEN, Keyboard.KeyId.YEN); - -// glfwToTeraMaps.put(KeyEvent.VK_CIRCUMFLEX, Keyboard.KeyId.CIRCUMFLEX); -// glfwToTeraMaps.put(KeyEvent.VK_AT, Keyboard.KeyId.AT); -// glfwToTeraMaps.put(KeyEvent.VK_COLON, Keyboard.KeyId.COLON); -// glfwToTeraMaps.put(KeyEvent.VK_UNDERLINE, Keyboard.KeyId.UNDERLINE); -// glfwToTeraMaps.put(KeyEvent.VK_KANJI, Keyboard.KeyId.KANJI); -// glfwToTeraMaps.put(KeyEvent.VK_STOP, Keyboard.KeyId.STOP); -// glfwToTeraMaps.put(KeyEvent.VK_AX, Keyboard.KeyId.AX); -// glfwToTeraMaps.put(KeyEvent.VK_UNLABELED, Keyboard.KeyId.UNLABELED); - -// glfwToTeraMaps.put(KeyEvent.VK_SECTION, Keyboard.KeyId.SECTION); -// glfwToTeraMaps.put(KeyEvent.VK_KP_COMMA, Keyboard.KeyId.NUMPAD_COMMA); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_DIVIDE, Keyboard.KeyId.NUMPAD_DIVIDE); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_PRINTSCREEN, Keyboard.KeyId.PRINT_SCREEN); - -// glfwToTeraMaps.put(KeyEvent.VK_FUNCTION, Keyboard.KeyId.FUNCTION); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_PAUSE, Keyboard.KeyId.PAUSE); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_HOME, Keyboard.KeyId.HOME); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_UP, Keyboard.KeyId.UP); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_PAGE_UP, Keyboard.KeyId.PAGE_UP); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_LEFT, Keyboard.KeyId.LEFT); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_RIGHT, Keyboard.KeyId.RIGHT); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_END, Keyboard.KeyId.END); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_DOWN, Keyboard.KeyId.DOWN); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_PAGE_DOWN, Keyboard.KeyId.PAGE_DOWN); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_INSERT, Keyboard.KeyId.INSERT); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_DELETE, Keyboard.KeyId.DELETE); -// glfwToTeraMaps.put(KeyEvent.VK_CLEAR, Keyboard.KeyId.CLEAR); - AWT_TO_TERA_MAPPING.put(KeyEvent.VK_META, Keyboard.KeyId.LEFT_META); -// AWT_TO_TERA_MAPPING.put(KeyEvent.VK_RIGHT_SUPER, Keyboard.KeyId.RIGHT_META); -// glfwToTeraMaps.put(KeyEvent.VK_APPS, Keyboard.KeyId.APPS); -// glfwToTeraMaps.put(KeyEvent.VK_POWER, Keyboard.KeyId.POWER); -// glfwToTeraMaps.put(KeyEvent.VK_SLEEP, Keyboard.KeyId.SLEEP); - - TIntIntHashMap controlMap = new TIntIntHashMap(); - controlMap.put(KeyEvent.KEY_LOCATION_LEFT, Keyboard.KeyId.LEFT_CTRL); - controlMap.put(KeyEvent.KEY_LOCATION_RIGHT, Keyboard.KeyId.RIGHT_CTRL); - AWT_TO_TERA_EXTRA.put(KeyEvent.VK_CONTROL, controlMap); - TIntIntHashMap shiftMap = new TIntIntHashMap(); - shiftMap.put(KeyEvent.KEY_LOCATION_LEFT, Keyboard.KeyId.LEFT_SHIFT); - shiftMap.put(KeyEvent.KEY_LOCATION_RIGHT, Keyboard.KeyId.RIGHT_SHIFT); - AWT_TO_TERA_EXTRA.put(KeyEvent.VK_SHIFT, shiftMap); - TIntIntHashMap altMap = new TIntIntHashMap(); - altMap.put(KeyEvent.KEY_LOCATION_LEFT, Keyboard.KeyId.LEFT_ALT); - altMap.put(KeyEvent.KEY_LOCATION_RIGHT, Keyboard.KeyId.RIGHT_ALT); - AWT_TO_TERA_EXTRA.put(KeyEvent.VK_ALT, altMap); - TIntIntHashMap metaMap = new TIntIntHashMap(); - metaMap.put(KeyEvent.KEY_LOCATION_LEFT, Keyboard.KeyId.LEFT_META); - metaMap.put(KeyEvent.KEY_LOCATION_RIGHT, Keyboard.KeyId.RIGHT_META); - AWT_TO_TERA_EXTRA.put(KeyEvent.VK_META, metaMap); - TIntIntHashMap equalsMap = new TIntIntHashMap(); - equalsMap.put(KeyEvent.KEY_LOCATION_NUMPAD, Keyboard.KeyId.NUMPAD_EQUALS); - equalsMap.put(KeyEvent.KEY_LOCATION_STANDARD, Keyboard.KeyId.EQUALS); - equalsMap.put(KeyEvent.KEY_LOCATION_UNKNOWN, Keyboard.KeyId.EQUALS); - AWT_TO_TERA_EXTRA.put(KeyEvent.VK_EQUALS, equalsMap); - TIntIntHashMap enterMap = new TIntIntHashMap(); - enterMap.put(KeyEvent.KEY_LOCATION_NUMPAD, Keyboard.KeyId.NUMPAD_ENTER); - enterMap.put(KeyEvent.KEY_LOCATION_STANDARD, Keyboard.KeyId.ENTER); - enterMap.put(KeyEvent.KEY_LOCATION_UNKNOWN, Keyboard.KeyId.ENTER); - AWT_TO_TERA_EXTRA.put(KeyEvent.VK_ENTER, enterMap); - } - - private Queue rawKeyQueue = Lists.newLinkedList(); - private Queue charQueue = Lists.newLinkedList(); - private TIntSet buttonStates = new TIntHashSet(); - - public AwtKeyboardDevice() { - } - - public void registerToAwtGlCanvas(AWTGLCanvas canvas) { - canvas.addKeyListener(new KeyListener() { - @Override - public void keyTyped(KeyEvent e) { - charQueue.offer(new CharKeyboardAction(e.getKeyChar())); - } - - @Override - public void keyPressed(KeyEvent e) { - awtKeyCallback(e.getExtendedKeyCode(), ButtonState.DOWN, e.getKeyLocation()); - } - - @Override - public void keyReleased(KeyEvent e) { - awtKeyCallback(e.getExtendedKeyCode(), ButtonState.UP, e.getKeyLocation()); - } - }); - } - - /** - * Callback receive key input events. - */ - public void awtKeyCallback(int key, ButtonState state, int location) { - int teraKey; - TIntIntHashMap extraMap = AWT_TO_TERA_EXTRA.get(key); - if (extraMap != null) { - teraKey = extraMap.get(key); - } else { - teraKey = AWT_TO_TERA_MAPPING.get(key); - } - Input input = InputType.KEY.getInput(teraKey); - - if (state == ButtonState.DOWN) { - buttonStates.add(teraKey); - } else if (state == ButtonState.UP) { - buttonStates.remove(teraKey); - } - - rawKeyQueue.offer(new RawKeyboardAction(input, state)); - } - - @Override - public boolean isKeyDown(int key) { - return buttonStates.contains(key); - } - - @Override - public Queue getInputQueue() { - Queue rawKeyboardActions = Lists.newLinkedList(); - RawKeyboardAction action; - while ((action = rawKeyQueue.poll()) != null) { - rawKeyboardActions.add(action); - } - return rawKeyboardActions; - } - - @Override - public Queue getCharInputQueue() { - Queue charActions = Lists.newLinkedList(); - CharKeyboardAction action; - while ((action = charQueue.poll()) != null) { - charActions.add(action); - } - return charActions; - } -} diff --git a/facades/TeraEd/src/main/java/org/terasology/editor/input/AwtMouseDevice.java b/facades/TeraEd/src/main/java/org/terasology/editor/input/AwtMouseDevice.java deleted file mode 100644 index a1beb6a2e6f..00000000000 --- a/facades/TeraEd/src/main/java/org/terasology/editor/input/AwtMouseDevice.java +++ /dev/null @@ -1,164 +0,0 @@ -// Copyright 2021 The Terasology Foundation -// SPDX-License-Identifier: Apache-2.0 - -package org.terasology.editor.input; - -import com.google.common.collect.Lists; -import gnu.trove.set.TIntSet; -import gnu.trove.set.hash.TIntHashSet; -import org.joml.Vector2d; -import org.joml.Vector2i; -import org.lwjgl.opengl.awt.AWTGLCanvas; -import org.terasology.engine.config.RenderingConfig; -import org.terasology.input.ButtonState; -import org.terasology.input.InputType; -import org.terasology.input.MouseInput; -import org.terasology.input.device.MouseAction; -import org.terasology.input.device.MouseDevice; - -import java.awt.event.MouseEvent; -import java.awt.event.MouseListener; -import java.awt.event.MouseMotionListener; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; -import java.util.Queue; - -/** - * Awt mouse device convertor. Handles mouse input via AWT's callbacks Handles mouse state. - */ -public class AwtMouseDevice implements MouseDevice, PropertyChangeListener { - private RenderingConfig renderingConfig; - private float uiScale; - private boolean mouseGrabbed; - private Queue queue = Lists.newLinkedList(); - - private TIntSet buttonStates = new TIntHashSet(); - - private double xPos; - private double yPos; - - private double xPosDelta; - private double yPosDelta; - - public AwtMouseDevice(RenderingConfig renderingConfig) { - this.renderingConfig = renderingConfig; - this.uiScale = renderingConfig.getUiScale() / 100f; - renderingConfig.subscribe(RenderingConfig.UI_SCALE, this); - } - - public void registerToAwtGlCanvas(AWTGLCanvas canvas) { - canvas.addMouseListener(new MouseListener() { - @Override - public void mouseClicked(MouseEvent e) { - } - - @Override - public void mousePressed(MouseEvent e) { - int button = e.getButton() - 1; - buttonStates.add(button); - MouseInput mouseInput = MouseInput.find(InputType.MOUSE_BUTTON, button); - queue.offer(new MouseAction(mouseInput, ButtonState.DOWN, getPosition())); - } - - @Override - public void mouseReleased(MouseEvent e) { - int button = e.getButton() - 1; - buttonStates.remove(button); - MouseInput mouseInput = MouseInput.find(InputType.MOUSE_BUTTON, button); - queue.offer(new MouseAction(mouseInput, ButtonState.UP, getPosition())); - } - - @Override - public void mouseEntered(MouseEvent e) { - - } - - @Override - public void mouseExited(MouseEvent e) { - - } - }); - canvas.addMouseMotionListener(new MouseMotionListener() { - @Override - public void mouseDragged(MouseEvent e) { - updateMouse(e.getX(), e.getY()); - } - - @Override - public void mouseMoved(MouseEvent e) { - updateMouse(e.getX(), e.getY()); - } - }); - - canvas.addMouseWheelListener(e -> { - int yOffset = e.getUnitsToScroll(); - if (yOffset != 0.0) { - int id = (yOffset > 0) ? 1 : -1; - queue.offer(new MouseAction(InputType.MOUSE_WHEEL.getInput(id), 1, getPosition())); - } - }); - } - - @Override - public void update() { - } - - private void updateMouse(double x, double y) { - xPosDelta = x - this.xPos; - yPosDelta = y - this.yPos; - this.xPos = x; - this.yPos = y; - } - - @Override - public Vector2i getPosition() { - return new Vector2i((int) (xPos / this.uiScale), (int) (yPos / this.uiScale)); - } - - @Override - public Vector2d getDelta() { - - Vector2d result = new Vector2d(xPosDelta, yPosDelta); - return result; - } - - @Override - public boolean isButtonDown(int button) { - return buttonStates.contains(button); - } - - @Override - public boolean isVisible() { - return !mouseGrabbed; - } - - @Override - public void setGrabbed(boolean newGrabbed) { - if (newGrabbed != mouseGrabbed) { - mouseGrabbed = newGrabbed; - // TODO handle swing mouse grabbing - } - } - - @Override - public Queue getInputQueue() { - Queue mouseActions = Lists.newLinkedList(); - MouseAction action; - while ((action = queue.poll()) != null) { - mouseActions.add(action); - } - return mouseActions; - } - - @Override - public void propertyChange(PropertyChangeEvent evt) { - if (evt.getPropertyName().equals(RenderingConfig.UI_SCALE)) { - this.uiScale = this.renderingConfig.getUiScale() / 100f; - } - } - - public void resetDelta() { - xPosDelta = 0; - yPosDelta = 0; - } -} diff --git a/facades/TeraEd/src/main/java/org/terasology/editor/properties/FloatProperty.java b/facades/TeraEd/src/main/java/org/terasology/editor/properties/FloatProperty.java deleted file mode 100644 index b9d7af11435..00000000000 --- a/facades/TeraEd/src/main/java/org/terasology/editor/properties/FloatProperty.java +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2021 The Terasology Foundation -// SPDX-License-Identifier: Apache-2.0 -package org.terasology.editor.properties; - -import org.terasology.reflection.metadata.FieldMetadata; - -import java.text.DecimalFormat; - -public class FloatProperty implements Property { - - private static final DecimalFormat DEFAULT_DECIMAL_FORMAT = new DecimalFormat("0.0000000"); - - private final float min; - private final float max; - private final T target; - private final FieldMetadata field; - - - public FloatProperty(T target, FieldMetadata field, float min, float max) { - this.target = target; - this.min = min; - this.max = max; - this.field = field; - } - - public float getMinValue() { - return min; - } - - public float getMaxValue() { - return max; - } - - @Override - public Float getValue() { - return field.getValueChecked(target); - } - - @Override - public void setValue(Float value) { - field.setValue(target, value); - } - - @Override - public Class getValueType() { - return Float.class; - } - - @Override - public String getTitle() { - return field.getName(); - } - - @Override - public String toString() { - return DEFAULT_DECIMAL_FORMAT.format(getValue()); - } -} diff --git a/facades/TeraEd/src/main/java/org/terasology/editor/properties/Property.java b/facades/TeraEd/src/main/java/org/terasology/editor/properties/Property.java deleted file mode 100644 index 6999f271ca2..00000000000 --- a/facades/TeraEd/src/main/java/org/terasology/editor/properties/Property.java +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2021 The Terasology Foundation -// SPDX-License-Identifier: Apache-2.0 -package org.terasology.editor.properties; - -public interface Property { - - T getValue(); - - void setValue(T value); - - Class getValueType(); - - String getTitle(); -} diff --git a/facades/TeraEd/src/main/java/org/terasology/editor/properties/PropertyProvider.java b/facades/TeraEd/src/main/java/org/terasology/editor/properties/PropertyProvider.java deleted file mode 100644 index 82e4f1b07c2..00000000000 --- a/facades/TeraEd/src/main/java/org/terasology/editor/properties/PropertyProvider.java +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2021 The Terasology Foundation -// SPDX-License-Identifier: Apache-2.0 -package org.terasology.editor.properties; - -import java.util.List; - -public interface PropertyProvider { - /** - * Adds the properties of this Object to the given property list. - * - * @return a list of the properties of this object - */ - List> getProperties(); -} diff --git a/facades/TeraEd/src/main/java/org/terasology/editor/properties/ReflectionProvider.java b/facades/TeraEd/src/main/java/org/terasology/editor/properties/ReflectionProvider.java deleted file mode 100644 index 62b9487abe8..00000000000 --- a/facades/TeraEd/src/main/java/org/terasology/editor/properties/ReflectionProvider.java +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2021 The Terasology Foundation -// SPDX-License-Identifier: Apache-2.0 -package org.terasology.editor.properties; - -import com.google.common.collect.Lists; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.terasology.engine.context.Context; -import org.terasology.nui.properties.Range; -import org.terasology.reflection.copy.CopyStrategyLibrary; -import org.terasology.reflection.metadata.ClassMetadata; -import org.terasology.reflection.metadata.DefaultClassMetadata; -import org.terasology.reflection.metadata.FieldMetadata; -import org.terasology.reflection.reflect.ReflectFactory; - -import java.lang.reflect.Field; -import java.util.List; - -import static com.google.common.base.Predicates.and; -import static com.google.common.base.Predicates.or; -import static org.reflections.ReflectionUtils.getAllFields; -import static org.reflections.ReflectionUtils.withAnnotation; -import static org.reflections.ReflectionUtils.withType; - -public class ReflectionProvider implements PropertyProvider { - private static final Logger logger = LoggerFactory.getLogger(ReflectionProvider.class); - - private List> properties = Lists.newArrayList(); - - public ReflectionProvider(T target, Context context) { - try { - ReflectFactory reflectFactory = context.get(ReflectFactory.class); - CopyStrategyLibrary copyStrategies = context.get(CopyStrategyLibrary.class); - ClassMetadata classMetadata = new DefaultClassMetadata<>("engine:empty", (Class) target.getClass(), - reflectFactory, copyStrategies); - for (Field field : getAllFields(target.getClass(), - and(withAnnotation(Range.class), or(withType(Float.TYPE), withType(Float.class))))) { - Range range = field.getAnnotation(Range.class); - FieldMetadata fieldMetadata = (FieldMetadata) classMetadata.getField(field.getName()); - Property property = new FloatProperty(target, fieldMetadata, range.min(), range.max()); - properties.add(property); - } - } catch (NoSuchMethodException e) { - logger.error("Cannot provide provide inspection for {}, does not have a default constructor", target.getClass()); - } - - } - - @Override - public List> getProperties() { - return properties; - } -} diff --git a/facades/TeraEd/src/main/java/org/terasology/editor/properties/SceneProperties.java b/facades/TeraEd/src/main/java/org/terasology/editor/properties/SceneProperties.java deleted file mode 100644 index 7430c242578..00000000000 --- a/facades/TeraEd/src/main/java/org/terasology/editor/properties/SceneProperties.java +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2021 The Terasology Foundation -// SPDX-License-Identifier: Apache-2.0 -package org.terasology.editor.properties; - -import com.google.common.collect.Lists; -import org.terasology.engine.context.Context; -import org.terasology.engine.core.TerasologyEngine; -import org.terasology.engine.core.modes.GameState; -import org.terasology.engine.core.modes.StateIngame; -import org.terasology.engine.rendering.backdrop.BackdropProvider; - -import java.util.List; - -public class SceneProperties implements PropertyProvider { - - private final TerasologyEngine engine; - - public SceneProperties(TerasologyEngine engine) { - this.engine = engine; - } - - @Override - public List> getProperties() { - List> result = Lists.newArrayList(); - - GameState gameState = engine.getState(); - if (!(gameState instanceof StateIngame)) { - return result; - } - StateIngame ingameState = (StateIngame) gameState; - Context ingameContext = ingameState.getContext(); - - BackdropProvider backdropProvider = ingameContext.get(BackdropProvider.class); - if (backdropProvider != null) { - result.addAll(new ReflectionProvider(backdropProvider, ingameContext).getProperties()); - } - - // TODO: fix this - /*FrameBuffersManager renderingProcess = ingameContext.get(FrameBuffersManager.class); - if (renderingProcess != null) { - result.addAll(new ReflectionProvider(renderingProcess, ingameContext).getProperties()); - }*/ - return result; - } -} diff --git a/facades/TeraEd/src/main/java/org/terasology/editor/subsystem/AwtInput.java b/facades/TeraEd/src/main/java/org/terasology/editor/subsystem/AwtInput.java deleted file mode 100644 index c2c6d5f4d2d..00000000000 --- a/facades/TeraEd/src/main/java/org/terasology/editor/subsystem/AwtInput.java +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2021 The Terasology Foundation -// SPDX-License-Identifier: Apache-2.0 -package org.terasology.editor.subsystem; - -import org.terasology.gestalt.assets.module.ModuleAwareAssetTypeManager; -import org.terasology.engine.config.Config; -import org.terasology.engine.config.ControllerConfig; -import org.terasology.engine.context.Context; -import org.terasology.editor.input.AwtKeyboardDevice; -import org.terasology.editor.input.AwtMouseDevice; -import org.terasology.engine.core.modes.GameState; -import org.terasology.engine.core.subsystem.config.BindsManager; -import org.terasology.engine.core.subsystem.lwjgl.BaseLwjglSubsystem; -import org.terasology.engine.input.InputSystem; -import org.terasology.engine.input.lwjgl.LwjglControllerDevice; - -public class AwtInput extends BaseLwjglSubsystem { - - private Context context; - - @Override - public String getName() { - return "Input"; - } - - @Override - public void registerCoreAssetTypes(ModuleAwareAssetTypeManager assetTypeManager) { - } - - @Override - public void postInitialise(Context rootContext) { - this.context = rootContext; - initControls(); - updateInputConfig(); - } - - @Override - public void postUpdate(GameState currentState, float delta) { - currentState.handleInput(delta); - } - - private void initControls() { - Config config = context.get(Config.class); - - InputSystem inputSystem = new InputSystem(); - context.put(InputSystem.class, inputSystem); - inputSystem.setMouseDevice(new AwtMouseDevice(config.getRendering())); - inputSystem.setKeyboardDevice(new AwtKeyboardDevice()); - - ControllerConfig controllerConfig = config.getInput().getControllers(); - LwjglControllerDevice controllerDevice = new LwjglControllerDevice(controllerConfig); - inputSystem.setControllerDevice(controllerDevice); - } - - private void updateInputConfig() { - BindsManager bindsManager = context.get(BindsManager.class); - bindsManager.updateConfigWithDefaultBinds(); - bindsManager.saveBindsConfig(); - } -} diff --git a/facades/TeraEd/src/main/java/org/terasology/editor/subsystem/LwjglPortlet.java b/facades/TeraEd/src/main/java/org/terasology/editor/subsystem/LwjglPortlet.java deleted file mode 100644 index 6668548d3e3..00000000000 --- a/facades/TeraEd/src/main/java/org/terasology/editor/subsystem/LwjglPortlet.java +++ /dev/null @@ -1,219 +0,0 @@ -// Copyright 2021 The Terasology Foundation -// SPDX-License-Identifier: Apache-2.0 -package org.terasology.editor.subsystem; - -import com.google.common.base.Preconditions; -import org.lwjgl.glfw.GLFW; -import org.lwjgl.glfw.GLFWImage; -import org.lwjgl.opengl.GL43; -import org.lwjgl.opengl.awt.AWTGLCanvas; -import org.lwjgl.opengl.awt.GLData; -import org.lwjgl.system.MemoryUtil; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.terasology.gestalt.assets.module.ModuleAwareAssetTypeManager; -import org.terasology.engine.config.Config; -import org.terasology.engine.config.RenderingConfig; -import org.terasology.engine.context.Context; -import org.terasology.editor.input.AwtKeyboardDevice; -import org.terasology.editor.input.AwtMouseDevice; -import org.terasology.engine.core.GameEngine; -import org.terasology.engine.core.GameThread; -import org.terasology.engine.core.TerasologyEngine; -import org.terasology.engine.core.modes.GameState; -import org.terasology.engine.core.subsystem.DisplayDevice; -import org.terasology.engine.core.subsystem.lwjgl.BaseLwjglSubsystem; -import org.terasology.engine.core.subsystem.lwjgl.DebugCallback; -import org.terasology.engine.core.subsystem.lwjgl.GLFWErrorCallback; -import org.terasology.engine.core.subsystem.lwjgl.LwjglGraphicsManager; -import org.terasology.engine.core.subsystem.lwjgl.LwjglGraphicsUtil; -import org.terasology.engine.entitySystem.event.internal.EventSystem; -import org.terasology.engine.input.InputSystem; -import org.terasology.nui.canvas.CanvasRenderer; -import org.terasology.engine.registry.CoreRegistry; -import org.terasology.engine.rendering.ShaderManager; -import org.terasology.engine.rendering.ShaderManagerLwjgl; -import org.terasology.engine.rendering.nui.internal.LwjglCanvasRenderer; - -import javax.imageio.ImageIO; -import java.awt.image.BufferedImage; -import java.io.IOException; - -import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT; -import static org.lwjgl.opengl.GL11.GL_DEPTH_BUFFER_BIT; -import static org.lwjgl.opengl.GL11.glClear; -import static org.lwjgl.opengl.GL11.glLoadIdentity; - -public class LwjglPortlet extends BaseLwjglSubsystem { - - private static final Logger logger = LoggerFactory.getLogger(LwjglPortlet.class); - - private Context context; - private RenderingConfig config; - - private GameEngine engine; - private AWTGLCanvas canvas; - private LwjglPortletDisplayDevice display; - private AwtMouseDevice mouseDevice; - - private final LwjglGraphicsManager graphics = new LwjglGraphicsManager(); - - @Override - public String getName() { - return "Portlet"; - } - - @Override - public void initialise(GameEngine gameEngine, Context rootContext) { - logger.info("Starting initialization of LWJGL"); - this.engine = gameEngine; - this.context = rootContext; - this.config = context.get(Config.class).getRendering(); - - graphics.setThreadMode(LwjglGraphicsManager.ThreadMode.DISPLAY_THREAD); - display = new LwjglPortletDisplayDevice(canvas, graphics); - context.put(DisplayDevice.class, display); - logger.info("Initial initialization complete"); - } - - @Override - public void registerCoreAssetTypes(ModuleAwareAssetTypeManager assetTypeManager) { - graphics.registerCoreAssetTypes(assetTypeManager); - } - - @Override - public void postInitialise(Context rootContext) { - graphics.registerRenderingSubsystem(context); - - initBuffer(); - - context.put(ShaderManager.class, new ShaderManagerLwjgl()); - context.put(CanvasRenderer.class, new LwjglCanvasRenderer(context)); - } - - @Override - public void postUpdate(GameState currentState, float delta) { - graphics.processActions(); - - currentState.render(); - - display.update(); - int frameLimit = context.get(Config.class).getRendering().getFrameLimit(); - // TODO: do we still need this? -// if (frameLimit > 0) { -// Lwjgl2Sync.sync(frameLimit); -// } - if (display.isCloseRequested()) { - engine.shutdown(); - } - } - - public void setupThreads() { - GameThread.reset(); - GameThread.setToCurrentThread(); - graphics.setThreadMode(LwjglGraphicsManager.ThreadMode.GAME_THREAD); - - EventSystem eventSystem = CoreRegistry.get(EventSystem.class); - if (eventSystem != null) { - eventSystem.setToCurrentThread(); - } - } - - public void createCanvas() { - GLData data = new GLData(); - data.samples = 4; - canvas = new AWTGLCanvas() { - @Override - public void initGL() { - initGLFW(); - initOpenGL(); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - glLoadIdentity(); - } - - @Override - public void paintGL() { - if (((TerasologyEngine) engine).tick()) { - mouseDevice.resetDelta(); - } - } - }; - } - - public AWTGLCanvas getCanvas() { - return this.canvas; - } - - private void initGLFW() { - if (!GLFW.glfwInit()) { - throw new RuntimeException("Failed to initialize GLFW"); - } - - GLFW.glfwDefaultWindowHints(); - GLFW.glfwWindowHint(GLFW.GLFW_VISIBLE, GLFW.GLFW_FALSE); - GLFW.glfwWindowHint(GLFW.GLFW_COCOA_GRAPHICS_SWITCHING, GLFW.GLFW_TRUE); - GLFW.glfwWindowHint(GLFW.GLFW_COCOA_RETINA_FRAMEBUFFER, GLFW.GLFW_FALSE); - GLFW.glfwWindowHint(GLFW.GLFW_DEPTH_BITS, config.getPixelFormat()); - - if (config.getDebug().isEnabled()) { - GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_DEBUG_CONTEXT, GLFW.GLFW_TRUE); - } - - GLFW.glfwSetErrorCallback(new GLFWErrorCallback()); - } - - private void initBuffer() { - logger.info("Initializing display (if last line in log then likely the game crashed from an issue with your " + - "video card)"); - - if (!config.isVSync()) { - GLFW.glfwSwapInterval(0); - } - - try { - String root = "org/terasology/icons/"; - ClassLoader classLoader = getClass().getClassLoader(); - - BufferedImage icon16 = ImageIO.read(classLoader.getResourceAsStream(root + "gooey_sweet_16.png")); - BufferedImage icon32 = ImageIO.read(classLoader.getResourceAsStream(root + "gooey_sweet_32.png")); - BufferedImage icon64 = ImageIO.read(classLoader.getResourceAsStream(root + "gooey_sweet_64.png")); - BufferedImage icon128 = ImageIO.read(classLoader.getResourceAsStream(root + "gooey_sweet_128.png")); - GLFWImage.Buffer buffer = GLFWImage.create(4); - buffer.put(0, LwjglGraphicsUtil.convertToGLFWFormat(icon16)); - buffer.put(1, LwjglGraphicsUtil.convertToGLFWFormat(icon32)); - buffer.put(2, LwjglGraphicsUtil.convertToGLFWFormat(icon64)); - buffer.put(3, LwjglGraphicsUtil.convertToGLFWFormat(icon128)); - - } catch (IOException | IllegalArgumentException e) { - logger.warn("Could not set icon", e); - } - - display.setDisplayModeSetting(config.getDisplayModeSetting()); - } - - private void initOpenGL() { - logger.info("Initializing OpenGL"); - LwjglGraphicsUtil.checkOpenGL(); - LwjglGraphicsUtil.initOpenGLParams(); - if (config.getDebug().isEnabled()) { - try { - GL43.glDebugMessageCallback(new DebugCallback(), MemoryUtil.NULL); - } catch (IllegalStateException e) { - logger.warn("Unable to specify DebugCallback to receive debugging messages from the GL."); - } - } - } - - public void initInputs() { - final InputSystem inputSystem = context.get(InputSystem.class); - Preconditions.checkNotNull(inputSystem); - mouseDevice = ((AwtMouseDevice) inputSystem.getMouseDevice()); - mouseDevice.registerToAwtGlCanvas(canvas); - ((AwtKeyboardDevice) inputSystem.getKeyboard()).registerToAwtGlCanvas(canvas); - } - - @Override - public void shutdown() { - GLFW.glfwTerminate(); - } -} diff --git a/facades/TeraEd/src/main/java/org/terasology/editor/subsystem/LwjglPortletDisplayDevice.java b/facades/TeraEd/src/main/java/org/terasology/editor/subsystem/LwjglPortletDisplayDevice.java deleted file mode 100644 index a0fe9ec2b66..00000000000 --- a/facades/TeraEd/src/main/java/org/terasology/editor/subsystem/LwjglPortletDisplayDevice.java +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright 2021 The Terasology Foundation -// SPDX-License-Identifier: Apache-2.0 - -package org.terasology.editor.subsystem; - -import org.lwjgl.opengl.GL11; -import org.lwjgl.opengl.awt.AWTGLCanvas; -import org.terasology.engine.core.subsystem.DisplayDevice; -import org.terasology.engine.core.subsystem.DisplayDeviceInfo; -import org.terasology.engine.core.subsystem.Resolution; -import org.terasology.engine.core.subsystem.lwjgl.LwjglDisplayDevice; -import org.terasology.engine.core.subsystem.lwjgl.LwjglGraphicsManager; -import org.terasology.engine.core.subsystem.lwjgl.LwjglResolution; -import org.terasology.engine.rendering.nui.layers.mainMenu.videoSettings.DisplayModeSetting; -import org.terasology.engine.utilities.subscribables.AbstractSubscribable; - -import java.awt.GraphicsEnvironment; -import java.awt.event.ComponentAdapter; -import java.awt.event.ComponentEvent; -import java.util.ArrayList; -import java.util.List; - -public class LwjglPortletDisplayDevice extends AbstractSubscribable implements DisplayDevice { - - private final AWTGLCanvas canvas; - private final LwjglGraphicsManager graphics; - - public LwjglPortletDisplayDevice(AWTGLCanvas canvas, LwjglGraphicsManager graphics) { - this.canvas = canvas; - this.graphics = graphics; - canvas.addComponentListener(new ComponentAdapter() { - @Override - public void componentResized(ComponentEvent e) { - updateViewport(); - } - }); - } - - @Override - public boolean hasFocus() { - return canvas.hasFocus(); - } - - @Override - public boolean isCloseRequested() { - return false; - } - - @Override - public void setFullscreen(boolean state) { - } - - @Override - public boolean isFullscreen() { - return false; - } - - @Override - public void setDisplayModeSetting(DisplayModeSetting displayModeSetting) { - } - - @Override - public DisplayModeSetting getDisplayModeSetting() { - return DisplayModeSetting.WINDOWED; - } - - @Override - public Resolution getResolution() { - GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment(); - int bitDepth = env.getDefaultScreenDevice().getDisplayMode().getBitDepth(); - int refreshRate = env.getDefaultScreenDevice().getDisplayMode().getRefreshRate(); - return new LwjglResolution(getWidth(), getHeight(), bitDepth, bitDepth, bitDepth, refreshRate); - } - - @Override - public List getResolutions() { - ArrayList resolutions = new ArrayList<>(); - resolutions.add(getResolution()); - return resolutions; - } - - @Override - public int getWidth() { - return canvas.getWidth(); - } - - @Override - public int getHeight() { - return canvas.getHeight(); - } - - @Override - public void setResolution(Resolution resolution) { - } - - @Override - public void processMessages() { - } - - @Override - public boolean isHeadless() { - return false; - } - - @Override - public void prepareToRender() { - } - - private void updateViewport() { - updateViewport(getWidth(), getHeight()); - } - - protected void updateViewport(int width, int height) { - graphics.asynchToDisplayThread(() -> { - GL11.glViewport(0, 0, width, height); - propertyChangeSupport.firePropertyChange(LwjglDisplayDevice.DISPLAY_RESOLUTION_CHANGE, 0, 1); - }); - } - - @Override - public void update() { - processMessages(); - canvas.swapBuffers(); - } - - @Override - public DisplayDeviceInfo getInfo() { - return graphics.getDisplayDeviceInfo(); - } -} diff --git a/facades/TeraEd/src/main/java/org/terasology/editor/ui/MainWindow.java b/facades/TeraEd/src/main/java/org/terasology/editor/ui/MainWindow.java deleted file mode 100644 index 0189c071437..00000000000 --- a/facades/TeraEd/src/main/java/org/terasology/editor/ui/MainWindow.java +++ /dev/null @@ -1,183 +0,0 @@ -// Copyright 2021 The Terasology Foundation -// SPDX-License-Identifier: Apache-2.0 -package org.terasology.editor.ui; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.terasology.editor.TeraEd; -import org.terasology.engine.core.StateChangeSubscriber; -import org.terasology.engine.core.TerasologyEngine; - -import javax.swing.JFrame; -import javax.swing.JMenu; -import javax.swing.JMenuBar; -import javax.swing.JMenuItem; -import javax.swing.JScrollPane; -import javax.swing.JSplitPane; -import javax.swing.ScrollPaneConstants; -import java.awt.BorderLayout; -import java.awt.Dimension; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.WindowEvent; -import java.awt.event.WindowListener; -import java.util.ArrayList; - -/** - * TeraEd main class. - */ -@SuppressWarnings("serial") -public final class MainWindow extends JFrame implements ActionListener, WindowListener, StateChangeSubscriber { - - private static final Logger logger = LoggerFactory.getLogger(MainWindow.class); - - private TeraEd teraEd; - private TerasologyEngine engine; - - private BorderLayout borderLayout; - private Viewport viewport; - private PropertyPanel propertyPanel; - - private JSplitPane verticalSplitPane; - - private JMenuBar mainMenuBar; - - private JMenu fileMenu; - private JMenuItem fileMenuExitItem; - - private JMenu shaderPropertiesMenu; - private java.util.List shaderPropertyMenuEntries = new ArrayList<>(64); - - private JMenu propertiesMenu; - private JMenuItem propertiesMenuScene; - - private JScrollPane propertyPanelScrollPane; - - public MainWindow(TeraEd teraEd, TerasologyEngine engine) { - this.teraEd = teraEd; - this.addWindowListener(this); - this.engine = engine; - - viewport = new Viewport(); - - borderLayout = new BorderLayout(); - getContentPane().setLayout(borderLayout); - - // Build up the main window editor layout... - propertyPanel = new PropertyPanel(); - - propertyPanelScrollPane = new JScrollPane( - propertyPanel, - ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, - ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); - propertyPanelScrollPane.setMinimumSize(new Dimension(350, 720)); - propertyPanelScrollPane.setPreferredSize(new Dimension(350, 720)); - - verticalSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, viewport, propertyPanelScrollPane); - verticalSplitPane.setContinuousLayout(true); - verticalSplitPane.setResizeWeight(0.5); - getContentPane().add(verticalSplitPane, BorderLayout.CENTER); - - setTitle("TeraEd - Terasology" + " | " + "Alpha"); - - mainMenuBar = new JMenuBar(); - setJMenuBar(mainMenuBar); - - fileMenu = new JMenu("File"); - - fileMenuExitItem = new JMenuItem("Exit"); - fileMenuExitItem.addActionListener(this); - fileMenu.add(fileMenuExitItem); - - shaderPropertiesMenu = new JMenu("Shader Properties"); - - propertiesMenu = new JMenu("Properties"); - - propertiesMenuScene = new JMenuItem("Scene"); - propertiesMenuScene.addActionListener(this); - propertiesMenu.add(propertiesMenuScene); - - mainMenuBar.add(fileMenu); - mainMenuBar.add(shaderPropertiesMenu); - mainMenuBar.add(propertiesMenu); - - pack(); - setVisible(true); - } - - public Viewport getViewport() { - return viewport; - } - - public void onStateChange() { - /* - shaderPropertyMenuEntries.clear(); - shaderPropertiesMenu.removeAll(); - GameState gameState = engine.getState(); - if (gameState instanceof StateIngame) { - StateIngame stateIngame = (StateIngame) gameState; - Context ingameContext = stateIngame.getContext(); - AssetManager assetManager = ingameContext.get(AssetManager.class); - for (Material material : assetManager.getLoadedAssets(Material.class)) { - GLSLMaterial finalMat = (GLSLMaterial) material; - if (finalMat.getShaderParameters() != null) { - final PropertyProvider provider = new ReflectionProvider(finalMat.getShaderParameters(), - ingameContext); - if (!provider.getProperties().isEmpty()) { - final String programName = material.getUrn().toString(); - JMenuItem menuItem = new JMenuItem(programName); - menuItem.addActionListener(e -> { - propertyPanel.setActivePropertyProvider(provider); - propertyPanel.setTitle(programName); - }); - shaderPropertyMenuEntries.add(menuItem); - shaderPropertiesMenu.add(menuItem); - } - } - } - } - */ - } - - @Override - public void actionPerformed(ActionEvent e) { - if (e.getSource() == fileMenuExitItem) { - teraEd.getEngine().shutdown(); - } else if (e.getSource() == propertiesMenuScene) { - propertyPanel.setActivePropertyProvider(teraEd.getSceneProperties()); - propertyPanel.setTitle("Scene Properties"); - } - } - - @Override - public void windowOpened(WindowEvent e) { - } - - @Override - public void windowClosing(WindowEvent e) { - teraEd.getEngine().shutdown(); - } - - @Override - public void windowClosed(WindowEvent e) { - } - - @Override - public void windowIconified(WindowEvent e) { - } - - @Override - public void windowDeiconified(WindowEvent e) { - } - - @Override - public void windowActivated(WindowEvent e) { - - } - - @Override - public void windowDeactivated(WindowEvent e) { - - } - -} diff --git a/facades/TeraEd/src/main/java/org/terasology/editor/ui/PropertyPanel.java b/facades/TeraEd/src/main/java/org/terasology/editor/ui/PropertyPanel.java deleted file mode 100644 index 1499b03edfe..00000000000 --- a/facades/TeraEd/src/main/java/org/terasology/editor/ui/PropertyPanel.java +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2021 The Terasology Foundation -// SPDX-License-Identifier: Apache-2.0 -package org.terasology.editor.ui; - -import org.terasology.editor.properties.FloatProperty; -import org.terasology.editor.properties.Property; -import org.terasology.editor.properties.PropertyProvider; - -import javax.swing.*; -import javax.swing.border.TitledBorder; -import java.awt.*; -import java.util.Iterator; -import java.util.List; - -public class PropertyPanel extends JPanel { - - private static final long serialVersionUID = 6844552770055484579L; - - private PropertyProvider activePropertyProvider; - - private TitledBorder border; - private String title = ""; - - public PropertyPanel() { - border = new TitledBorder(""); - setBorder(border); - } - - public PropertyPanel(String title) { - this(); - this.title = title; - border.setTitle(title); - } - - public PropertyPanel(PropertyProvider provider) { - this(); - setActivePropertyProvider(provider); - } - - public void setActivePropertyProvider(PropertyProvider provider) { - activePropertyProvider = provider; - onActivePropertyProviderChanged(); - } - - public void onActivePropertyProviderChanged() { - removeAll(); - - if (activePropertyProvider != null) { - List> properties = activePropertyProvider.getProperties(); - - setLayout(new GridLayout(properties.size() >= 16 ? properties.size() : 16, 1)); - - Iterator> it = properties.iterator(); - while (it.hasNext()) { - Property property = it.next(); - - if (property instanceof FloatProperty) { - add(new PropertySlider((FloatProperty) property)); - revalidate(); - } - } - } - - repaint(); - } - - public void setTitle(String title) { - this.title = title; - border.setTitle(title); - revalidate(); - } -} diff --git a/facades/TeraEd/src/main/java/org/terasology/editor/ui/PropertySlider.java b/facades/TeraEd/src/main/java/org/terasology/editor/ui/PropertySlider.java deleted file mode 100644 index 28eb12f2922..00000000000 --- a/facades/TeraEd/src/main/java/org/terasology/editor/ui/PropertySlider.java +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2021 The Terasology Foundation -// SPDX-License-Identifier: Apache-2.0 -package org.terasology.editor.ui; - -import org.terasology.editor.properties.FloatProperty; - -import javax.swing.*; -import javax.swing.border.TitledBorder; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; -import java.awt.*; - -public class PropertySlider extends JPanel implements ChangeListener { - - private static final long serialVersionUID = 3157887601371629996L; - - private JSlider slider; - private FloatProperty activeProperty; - - private BorderLayout borderLayout; - private TitledBorder titledBorder; - private JLabel label; - - public PropertySlider() { - titledBorder = new TitledBorder(""); - setBorder(titledBorder); - - borderLayout = new BorderLayout(); - setLayout(borderLayout); - - label = new JLabel(""); - - slider = new JSlider(); - slider.setMinimum(0); - slider.setMaximum(100); - slider.setMinorTickSpacing(1); - slider.setMajorTickSpacing(10); - slider.addChangeListener(this); - - add(slider, BorderLayout.CENTER); - add(label, BorderLayout.EAST); - } - - public PropertySlider(FloatProperty property) { - this(); - setActiveProperty(property); - } - - public void setActiveProperty(FloatProperty provider) { - activeProperty = provider; - onActivePropertyChanged(); - } - - public void onActivePropertyChanged() { - if (activeProperty != null) { - titledBorder.setTitle(activeProperty.getTitle()); - if (activeProperty.getValueType() == Float.class) { - setValue(activeProperty.getValue(), activeProperty.getMinValue(), activeProperty.getMaxValue()); - } - } - } - - public void setValue(float value, float minValue, float maxValue) { - int sliderValue = (int) (((value - minValue) / (maxValue - minValue)) * 100.0f); - slider.setValue(sliderValue); - } - - @Override - public void stateChanged(ChangeEvent e) { - if (activeProperty.getValueType() == Float.class) { - float range = Math.abs(activeProperty.getMaxValue() - activeProperty.getMinValue()); - float val = (slider.getValue() / 100.0f) * range + activeProperty.getMinValue(); - activeProperty.setValue(val); - label.setText(activeProperty.toString()); - } - } -} diff --git a/facades/TeraEd/src/main/java/org/terasology/editor/ui/Viewport.java b/facades/TeraEd/src/main/java/org/terasology/editor/ui/Viewport.java deleted file mode 100644 index 0d51a349df1..00000000000 --- a/facades/TeraEd/src/main/java/org/terasology/editor/ui/Viewport.java +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2021 The Terasology Foundation -// SPDX-License-Identifier: Apache-2.0 -package org.terasology.editor.ui; - -import org.lwjgl.opengl.awt.AWTGLCanvas; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.SwingConstants; -import java.awt.BorderLayout; -import java.awt.Dimension; - -/** - * TeraEd main class. - */ -@SuppressWarnings("serial") -public final class Viewport extends JPanel { - - private static final Logger logger = LoggerFactory.getLogger(Viewport.class); - private JLabel startingLabel = new JLabel("Starting Terasology..."); - - public Viewport() { - setLayout(new BorderLayout()); - setSize(1280, 720); - setMinimumSize(new Dimension(640, 480)); - setPreferredSize(new Dimension(1280, 720)); - setOpaque(false); - startingLabel.setHorizontalAlignment(SwingConstants.CENTER); - add(startingLabel, BorderLayout.CENTER); - } - - public void setTerasology(AWTGLCanvas canvas) { - remove(startingLabel); - add(canvas, BorderLayout.CENTER); - revalidate(); - } -} diff --git a/facades/TeraEd/src/main/resources/logback.xml b/facades/TeraEd/src/main/resources/logback.xml deleted file mode 100644 index a4125a5cf1a..00000000000 --- a/facades/TeraEd/src/main/resources/logback.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - true - - - - - - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - - - - - - - - - - - From 85e64dfdc57c0c12d4c3e3084294c7347a09490e Mon Sep 17 00:00:00 2001 From: jdrueckert Date: Sat, 15 Jul 2023 11:59:19 +0200 Subject: [PATCH 021/100] ci: configure jenkins to abort builds if new builds are triggered (#5119) --- Jenkinsfile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index fcc740194c8..a3e573e3f2c 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -12,7 +12,9 @@ properties([ // that can't simply be turned off copyArtifactPermission('*'), // Flag for Jenkins to discard attached artifacts after x builds - buildDiscarder(logRotator(artifactNumToKeepStr: artifactBuildsToKeep)) + buildDiscarder(logRotator(artifactNumToKeepStr: artifactBuildsToKeep)), + // configure Jenkins to abort a build if a new one is triggered for the same branch + disableConcurrentBuilds(abortPrevious: true) ]) /** From 828cd51be09e8def5f0ff543a73b5652ab23aef6 Mon Sep 17 00:00:00 2001 From: Tobias Nett Date: Sat, 15 Jul 2023 13:54:56 +0200 Subject: [PATCH 022/100] refactor(netork)!: rewrite ping logic (#5106) * refactor(network): use Map in PingStockComponent We have introduced type handlers for generic maps some time ago, and there is no need anymore to keep the ping information as separate lists. Therefore, refactor the internal storage of ping data to use a `Map`. * refactor(network): restructure and document ServerPingSystem * fix: more resilience on empty maps * refactor(network)!: merge PingSubscriberComponent and PingStockComponent into PingComponent * feat(nui): improve online players visuals * doc(nui): docs for OnlinePlayersOverlay --- .../engine/network/PingComponent.java | 34 +++++ .../engine/network/PingStockComponent.java | 50 ------- .../network/PingSubscriberComponent.java | 14 -- .../engine/network/ServerPingSystem.java | 125 ++++++++++-------- .../layers/ingame/OnlinePlayersOverlay.java | 119 ++++++++--------- .../engine/assets/ui/onlinePlayersOverlay.ui | 2 +- .../coreTypes/GenericMapTypeHandler.java | 11 +- 7 files changed, 171 insertions(+), 184 deletions(-) create mode 100644 engine/src/main/java/org/terasology/engine/network/PingComponent.java delete mode 100644 engine/src/main/java/org/terasology/engine/network/PingStockComponent.java delete mode 100644 engine/src/main/java/org/terasology/engine/network/PingSubscriberComponent.java diff --git a/engine/src/main/java/org/terasology/engine/network/PingComponent.java b/engine/src/main/java/org/terasology/engine/network/PingComponent.java new file mode 100644 index 00000000000..d649935a44a --- /dev/null +++ b/engine/src/main/java/org/terasology/engine/network/PingComponent.java @@ -0,0 +1,34 @@ +// Copyright 2023 The Terasology Foundation +// SPDX-License-Identifier: Apache-2.0 +package org.terasology.engine.network; + +import org.terasology.engine.entitySystem.entity.EntityRef; +import org.terasology.gestalt.entitysystem.component.Component; + +import java.util.HashMap; +import java.util.Map; + +/** + * PingStockComponent stock the ping information of one user. + *

+ * Might be used to stock ping information and display it in future. + */ +public final class PingComponent implements Component { + + @Replicate + private Map pings = new HashMap<>(); + + public void setValues(Map values) { + pings.clear(); + pings.putAll(values); + } + + public Map getValues() { + return new HashMap<>(pings); + } + + @Override + public void copyFrom(PingComponent other) { + this.setValues(other.getValues()); + } +} diff --git a/engine/src/main/java/org/terasology/engine/network/PingStockComponent.java b/engine/src/main/java/org/terasology/engine/network/PingStockComponent.java deleted file mode 100644 index 46608e283cf..00000000000 --- a/engine/src/main/java/org/terasology/engine/network/PingStockComponent.java +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2021 The Terasology Foundation -// SPDX-License-Identifier: Apache-2.0 -package org.terasology.engine.network; - -import org.terasology.engine.entitySystem.entity.EntityRef; -import org.terasology.gestalt.entitysystem.component.Component; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * PingStockComponent stock the ping information of one user. - *

- * Might be used to stock ping information and display it in future. - */ -public final class PingStockComponent implements Component { - - // TODO Map is not supported for replication (no type handler), - // therefore keys and values are replicated via lists. - // Not the best solution for performance but for <100 players and low update rates it should do the job - - @Replicate - private List pingKeys = new ArrayList<>(); - @Replicate - private List pingValues = new ArrayList<>(); - - public void setValues(Map values) { - pingKeys.clear(); - pingValues.clear(); - for (Map.Entry entry : values.entrySet()) { - pingKeys.add(entry.getKey()); - pingValues.add(entry.getValue()); - } - } - - public Map getValues() { - Map returnValues = new HashMap<>(); - for (int i = 0; i < pingKeys.size(); i++) { - returnValues.put(pingKeys.get(i), pingValues.get(i)); - } - return returnValues; - } - - @Override - public void copyFrom(PingStockComponent other) { - this.setValues(other.getValues()); - } -} diff --git a/engine/src/main/java/org/terasology/engine/network/PingSubscriberComponent.java b/engine/src/main/java/org/terasology/engine/network/PingSubscriberComponent.java deleted file mode 100644 index 34e11715a8c..00000000000 --- a/engine/src/main/java/org/terasology/engine/network/PingSubscriberComponent.java +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2021 The Terasology Foundation -// SPDX-License-Identifier: Apache-2.0 -package org.terasology.engine.network; - -import org.terasology.gestalt.entitysystem.component.EmptyComponent; - -/** - * PingSubscriberComponent, only on the server system, will be added to a client entity when this client subscribe. - * Server will only send ping information to the clients subscribed. - *

- * It can be used to stock the ping information of users in future. - */ -public class PingSubscriberComponent extends EmptyComponent { -} diff --git a/engine/src/main/java/org/terasology/engine/network/ServerPingSystem.java b/engine/src/main/java/org/terasology/engine/network/ServerPingSystem.java index 31b6ff2fcd2..ccf0b876ee6 100644 --- a/engine/src/main/java/org/terasology/engine/network/ServerPingSystem.java +++ b/engine/src/main/java/org/terasology/engine/network/ServerPingSystem.java @@ -1,4 +1,4 @@ -// Copyright 2021 The Terasology Foundation +// Copyright 2023 The Terasology Foundation // SPDX-License-Identifier: Apache-2.0 package org.terasology.engine.network; @@ -8,7 +8,6 @@ import org.terasology.engine.entitySystem.systems.RegisterMode; import org.terasology.engine.entitySystem.systems.RegisterSystem; import org.terasology.engine.entitySystem.systems.UpdateSubscriberSystem; -import org.terasology.engine.logic.players.LocalPlayer; import org.terasology.engine.network.events.DisconnectedEvent; import org.terasology.engine.network.events.PingFromClientEvent; import org.terasology.engine.network.events.PingFromServerEvent; @@ -25,18 +24,21 @@ /** * This system implement the server ping to clients on need base. * It runs on the server, pings to all clients who subscribe this function. + * + * @see PingFromServerEvent + * @see PingFromClientEvent + * @see SubscribePingEvent + * @see UnSubscribePingEvent */ @RegisterSystem(RegisterMode.AUTHORITY) public class ServerPingSystem extends BaseComponentSystem implements UpdateSubscriberSystem { + /** The interval in which pings are sent, in milliseconds. */ private static final long PING_PERIOD = 200; @In private EntityManager entityManager; - @In - private LocalPlayer localPlayer; - private Map startMap = new HashMap<>(); private Map endMap = new HashMap<>(); @@ -52,54 +54,77 @@ public void initialise() { @Override public void update(float delta) { - long time = Duration.between(lastPingTime, Instant.now()).toMillis(); + Instant now = Instant.now(); + long time = Duration.between(lastPingTime, now).toMillis(); if (time > PING_PERIOD) { - - // Server ping to all clients only if there are clients who subscribe - if (entityManager.getCountOfEntitiesWith(PingSubscriberComponent.class) != 0) { - Iterable clients = entityManager.getEntitiesWith(ClientComponent.class); - for (EntityRef client : clients) { - if (client.equals(localPlayer.getClientEntity())) { - continue; - } - - // send ping only if client replied the last ping - Instant lastPingFromClient = endMap.get(client); - Instant lastPingToClient = startMap.get(client); - // Only happens when server doesn't receive ping back yet - if (lastPingFromClient != null && lastPingToClient != null && lastPingFromClient.isBefore(lastPingToClient)) { - continue; - } - - Instant start = Instant.now(); - startMap.put(client, start); - client.send(new PingFromServerEvent()); - } + // only collect ping information if anybody is interested + if (entityManager.getCountOfEntitiesWith(PingComponent.class) > 0) { + startPings(); + updateSubscribers(); + } else { + clear(); } + lastPingTime = now; + } + } - //update ping data for all clients - for (EntityRef client : entityManager.getEntitiesWith(PingSubscriberComponent.class)) { - PingStockComponent pingStockComponent; - if (!client.hasComponent(PingStockComponent.class)) { - pingStockComponent = new PingStockComponent(); - } else { - pingStockComponent = client.getComponent(PingStockComponent.class); - } - if (localPlayer != null && localPlayer.getClientEntity() != null) { - pingMap.put(localPlayer.getClientEntity(), new Long(5)); - } - pingStockComponent.setValues(pingMap); - client.addOrSaveComponent(pingStockComponent); - } + /** + * Clear internal maps, for instance, when there are no more subscribers. + */ + private void clear() { + startMap.clear(); + endMap.clear(); + pingMap.clear(); + } - lastPingTime = Instant.now(); + /** + * Send a ping signal ({@link PingFromServerEvent}) from the server to all + * clients. + * + * Any entity with a {@link PingComponent} is considered as subscriber. + * + * Clients are supposed to answer with {@link PingFromClientEvent} to confirm + * the ping. + */ + private void startPings() { + for (EntityRef client : entityManager.getEntitiesWith(ClientComponent.class)) { + sendPingToClient(client); + } + } + + /** + * Send a ping signal to the client. + */ + private void sendPingToClient(EntityRef client) { + Instant lastPingFromClient = endMap.get(client); + Instant lastPingToClient = startMap.get(client); + // Send ping only if the client has replied to the last ping. This happens when + // there is still a ping in-flight, that is, the server hasn't received an answer + // from this client yet. + if (lastPingFromClient != null && lastPingToClient != null + && lastPingFromClient.isBefore(lastPingToClient)) { + return; + } + + startMap.put(client, Instant.now()); + client.send(new PingFromServerEvent()); + } + + /** + * Update the ping stock ({@link PingComponent}) on all subscribers + */ + private void updateSubscribers() { + for (EntityRef client : entityManager.getEntitiesWith(PingComponent.class)) { + client.updateComponent(PingComponent.class, pingComponent -> { + pingComponent.setValues(pingMap); + return pingComponent; + }); } } @ReceiveEvent(components = ClientComponent.class) public void onPingFromClient(PingFromClientEvent event, EntityRef entity) { - Instant end = Instant.now(); - endMap.put(entity, end); + endMap.put(entity, Instant.now()); updatePing(entity); } @@ -119,19 +144,11 @@ public void onDisconnected(DisconnectedEvent event, EntityRef entity) { @ReceiveEvent(components = ClientComponent.class) public void onSubscribePing(SubscribePingEvent event, EntityRef entity) { - entity.addOrSaveComponent(new PingSubscriberComponent()); + entity.addOrSaveComponent(new PingComponent()); } @ReceiveEvent(components = ClientComponent.class) public void onUnSubscribePing(UnSubscribePingEvent event, EntityRef entity) { - entity.removeComponent(PingSubscriberComponent.class); - entity.removeComponent(PingStockComponent.class); - - //if there is no pingSubscriber, then clean the map - if (entityManager.getCountOfEntitiesWith(PingSubscriberComponent.class) == 0) { - startMap.clear(); - endMap.clear(); - pingMap.clear(); - } + entity.removeComponent(PingComponent.class); } } diff --git a/engine/src/main/java/org/terasology/engine/rendering/nui/layers/ingame/OnlinePlayersOverlay.java b/engine/src/main/java/org/terasology/engine/rendering/nui/layers/ingame/OnlinePlayersOverlay.java index fef906ab13a..ac5097062f4 100644 --- a/engine/src/main/java/org/terasology/engine/rendering/nui/layers/ingame/OnlinePlayersOverlay.java +++ b/engine/src/main/java/org/terasology/engine/rendering/nui/layers/ingame/OnlinePlayersOverlay.java @@ -1,34 +1,33 @@ -// Copyright 2021 The Terasology Foundation +// Copyright 2023 The Terasology Foundation // SPDX-License-Identifier: Apache-2.0 package org.terasology.engine.rendering.nui.layers.ingame; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.terasology.engine.entitySystem.entity.EntityManager; import org.terasology.engine.entitySystem.entity.EntityRef; import org.terasology.engine.logic.afk.AfkComponent; import org.terasology.engine.logic.players.LocalPlayer; import org.terasology.engine.logic.players.PlayerUtil; import org.terasology.engine.network.ClientComponent; -import org.terasology.engine.network.PingStockComponent; +import org.terasology.engine.network.PingComponent; import org.terasology.engine.network.events.SubscribePingEvent; import org.terasology.engine.network.events.UnSubscribePingEvent; +import org.terasology.engine.registry.In; +import org.terasology.engine.rendering.nui.CoreScreenLayer; import org.terasology.nui.Color; import org.terasology.nui.FontColor; import org.terasology.nui.databinding.ReadOnlyBinding; import org.terasology.nui.widgets.UIText; -import org.terasology.engine.registry.In; -import org.terasology.engine.rendering.nui.CoreScreenLayer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; import java.util.Map; /** - * Overlay that lists all players that are currently online. + * Overlay that lists all players that are currently online and their pings. */ public class OnlinePlayersOverlay extends CoreScreenLayer { - private static final Logger logger = LoggerFactory.getLogger(OnlinePlayersOverlay.class); - private UIText text; @In @@ -43,69 +42,65 @@ public void initialise() { text.bindText(new ReadOnlyBinding() { @Override public String get() { - logger.info("localPlayer is: {}", localPlayer); - PingStockComponent pingStockComp = localPlayer.getClientEntity().getComponent(PingStockComponent.class); - if (pingStockComp == null) { - String playerListText = determinePlayerListText(); - return playerListText; - } else { - String playerAndPing = determinePlayerAndPing(pingStockComp); - return playerAndPing; - } + PingComponent pingComponent = localPlayer.getClientEntity().getComponent(PingComponent.class); + return determinePlayerList(getPingMap(pingComponent)); } }); } - private String determinePlayerListText() { - Iterable allClients = entityManager.getEntitiesWith(ClientComponent.class); - StringBuilder sb = new StringBuilder(); - boolean first = true; - for (EntityRef clientEntity : allClients) { - if (!first) { - sb.append("\n"); + /** + * Assemble a map from connected players (or clients) to their ping. + * + * If the ping component is null, the connected clients are determined by looking at entities with the {@link ClientComponent}. + * In this case, the ping values are {@code null}. + * + * @param pingComponent component with information on connected players, or {@code null} if not present + * @return a mapping from connected clients to their respective ping (or {@code null} if the ping cannot be determined) + */ + private Map getPingMap(PingComponent pingComponent) { + //TODO: There's a noticeable delay when opening the overlay before the first ping comes in and all players are shown. + // We could either try to match the entity refs here, or pre-fill the component sooner in the ServerPingSystem. + if (pingComponent != null) { + return pingComponent.getValues(); + } else { + Map pings = new HashMap<>(); + for (EntityRef client : entityManager.getEntitiesWith(ClientComponent.class)) { + pings.put(client, null); } - ClientComponent clientComp = clientEntity.getComponent(ClientComponent.class); - sb.append(PlayerUtil.getColoredPlayerName(clientComp.clientInfo)); - first = false; + return pings; } - return sb.toString(); } - private String determinePlayerAndPing(PingStockComponent pingStockComponent) { - Map pingMap = pingStockComponent.getValues(); - StringBuilder sb = new StringBuilder(); - boolean first = true; - for (Map.Entry entry : pingMap.entrySet()) { - EntityRef clientEntity = entry.getKey(); - if (clientEntity == null || clientEntity.getComponent(ClientComponent.class) == null) { - logger.warn("OnlinePlayersOverlay skipping a null client entity or component"); - continue; - } + /** + * Create multi-line string, containing one line per connected player. + */ + private String determinePlayerList(Map pings) { + List lines = new ArrayList<>(); + for (Map.Entry entry : pings.entrySet()) { + lines.add(determinePlayerLine(entry.getKey(), entry.getValue())); + } + return String.join("\n", lines); + } - if (!first) { - sb.append("\n"); - } + /** + * Create a single-line string with the player name and their ping. + * + *

+     *      [AFK]    Player4612                           42ms
+     *      -------- -------------------------------- --------
+     *         8                  32                      8
+     * 
+ */ + private String determinePlayerLine(EntityRef client, Long ping) { + ClientComponent clientComp = client.getComponent(ClientComponent.class); + AfkComponent afkComponent = client.getComponent(AfkComponent.class); - ClientComponent clientComp = clientEntity.getComponent(ClientComponent.class); - AfkComponent afkComponent = clientEntity.getComponent(AfkComponent.class); - if (afkComponent != null) { - if (afkComponent.afk) { - sb.append(FontColor.getColored("[AFK]", Color.red)); - sb.append(" "); - } - } - sb.append(PlayerUtil.getColoredPlayerName(clientComp.clientInfo)); - sb.append(" "); - Long pingValue = pingMap.get(clientEntity); - if (pingValue == null) { - sb.append("-"); - } else { - sb.append(pingValue.toString()); - sb.append("ms"); - } - first = false; - } - return sb.toString(); + String prefix = (afkComponent != null && afkComponent.afk) ? FontColor.getColored("[AFK]", Color.red) : ""; + String displayName = PlayerUtil.getColoredPlayerName(clientComp.clientInfo); + String displayPing = (ping != null) ? ping + "ms" : FontColor.getColored("---", Color.grey); + //TODO: the formatting does not work well since we're not using a mono-spaced font. we should investigate whether we can use + // a different UI element or at least a monospaced font to align prefix, player name, and ping better. + return String.format("%-8s%-32s%8s", prefix, displayName, displayPing); } @Override diff --git a/engine/src/main/resources/org/terasology/engine/assets/ui/onlinePlayersOverlay.ui b/engine/src/main/resources/org/terasology/engine/assets/ui/onlinePlayersOverlay.ui index 22b6268545f..75a5b4342d9 100644 --- a/engine/src/main/resources/org/terasology/engine/assets/ui/onlinePlayersOverlay.ui +++ b/engine/src/main/resources/org/terasology/engine/assets/ui/onlinePlayersOverlay.ui @@ -7,7 +7,7 @@ { "type": "UIBox", "layoutInfo": { - "width": 200, + "width": 400, "use-content-height": true, "position-horizontal-center": {}, "position-vertical-center": {} diff --git a/subsystems/TypeHandlerLibrary/src/main/java/org/terasology/persistence/typeHandling/coreTypes/GenericMapTypeHandler.java b/subsystems/TypeHandlerLibrary/src/main/java/org/terasology/persistence/typeHandling/coreTypes/GenericMapTypeHandler.java index 0c75399f6de..ebec6d4dd64 100644 --- a/subsystems/TypeHandlerLibrary/src/main/java/org/terasology/persistence/typeHandling/coreTypes/GenericMapTypeHandler.java +++ b/subsystems/TypeHandlerLibrary/src/main/java/org/terasology/persistence/typeHandling/coreTypes/GenericMapTypeHandler.java @@ -62,13 +62,18 @@ private PersistedData serializeEntry(Map.Entry entry, PersistedDataSeriali @Override public Optional> deserialize(PersistedData data) { + + Map result = Maps.newLinkedHashMap(); + + if (data.isNull()) { + return Optional.of(result); + } + if (!data.isArray() || data.isValueMap()) { logger.warn("Incorrect map format detected: object instead of array.\n" + getUsageInfo(data)); return Optional.empty(); } - Map result = Maps.newLinkedHashMap(); - for (PersistedData entry : data.getAsArray()) { PersistedDataMap kvEntry = entry.getAsValueMap(); PersistedData rawKey = kvEntry.get(KEY); @@ -101,7 +106,7 @@ private String getUsageInfo(PersistedData data) { " \"mapName\": [\n" + " { \"key\": \"...\", \"value\": \"...\" }\n" + " ]\n" + - "but found \n'{}'" + data + "'"; + "but found \n'" + data + "'"; } @Override From 1d831ead54b3883a149f3671d07819a3d43c4637 Mon Sep 17 00:00:00 2001 From: Tobias Nett Date: Sun, 16 Jul 2023 16:23:09 +0200 Subject: [PATCH 023/100] test(TypeHandler): More tests for empty collections (#5117) Our type handlers should be able to deserialize the empty array `[ ]` correctly into different collection formats, for instance, basic `Array`s, collections like `List`, or `Map`. This PR adds more tests to ensure that this is indeed the case. Closes #2343. --- .../prefabs/withEmptyListContainer.prefab | 6 +++++ .../engine/entitySystem/PrefabTest.java | 8 ++++++ .../coreTypes/ArrayTypeHandlerTest.java | 23 +++++++++++++++++ .../coreTypes/CollectionTypeHandlerTest.java | 25 +++++++++++++++++++ .../coreTypes/GenericMapTypeHandlerTest.java | 19 ++++++++++++++ 5 files changed, 81 insertions(+) create mode 100644 engine-tests/src/main/resources/org/terasology/unittest/assets/prefabs/withEmptyListContainer.prefab diff --git a/engine-tests/src/main/resources/org/terasology/unittest/assets/prefabs/withEmptyListContainer.prefab b/engine-tests/src/main/resources/org/terasology/unittest/assets/prefabs/withEmptyListContainer.prefab new file mode 100644 index 00000000000..c8926066d38 --- /dev/null +++ b/engine-tests/src/main/resources/org/terasology/unittest/assets/prefabs/withEmptyListContainer.prefab @@ -0,0 +1,6 @@ +{ + "ListOfObject" : { + "shortName" : "Test", + "elements" : [ ] + } +} diff --git a/engine-tests/src/test/java/org/terasology/engine/entitySystem/PrefabTest.java b/engine-tests/src/test/java/org/terasology/engine/entitySystem/PrefabTest.java index 00fc3d99cee..8796182566d 100644 --- a/engine-tests/src/test/java/org/terasology/engine/entitySystem/PrefabTest.java +++ b/engine-tests/src/test/java/org/terasology/engine/entitySystem/PrefabTest.java @@ -142,6 +142,14 @@ public void testPrefabWithListOfMappedContainers() { assertEquals("returnHome", mappedContainer.elements.get(1).id); } + + @Test + public void testPrefabWithEmptyListOfMappedContainers() { + Prefab prefab = prefabManager.getPrefab("unittest:withEmptyListContainer"); + ListOfObjectComponent mappedContainer = prefab.getComponent(ListOfObjectComponent.class); + assertEquals(0, mappedContainer.elements.size()); + } + @Test public void testPrefabWithListOfEnums() { Prefab prefab = prefabManager.getPrefab("unittest:withListEnumContainer"); diff --git a/subsystems/TypeHandlerLibrary/src/test/java/org/terasology/persistence/typeHandling/coreTypes/ArrayTypeHandlerTest.java b/subsystems/TypeHandlerLibrary/src/test/java/org/terasology/persistence/typeHandling/coreTypes/ArrayTypeHandlerTest.java index 7f3d0cbd1e7..faee29617cf 100644 --- a/subsystems/TypeHandlerLibrary/src/test/java/org/terasology/persistence/typeHandling/coreTypes/ArrayTypeHandlerTest.java +++ b/subsystems/TypeHandlerLibrary/src/test/java/org/terasology/persistence/typeHandling/coreTypes/ArrayTypeHandlerTest.java @@ -4,16 +4,23 @@ import gnu.trove.list.TIntList; import gnu.trove.list.array.TIntArrayList; + +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.mockito.ArgumentMatcher; import org.terasology.persistence.typeHandling.PersistedData; import org.terasology.persistence.typeHandling.PersistedDataSerializer; import org.terasology.persistence.typeHandling.inMemory.arrays.PersistedIntegerArray; +import org.terasology.persistence.typeHandling.inMemory.arrays.PersistedValueArray; import org.terasology.reflection.TypeInfo; +import java.lang.reflect.Array; import java.util.Collection; import java.util.Collections; +import java.util.List; +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth8.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.mock; @@ -65,4 +72,20 @@ void testDeserialize() { verify(elementTypeHandler, times(intList.size())).deserialize(any()); } + + @Test + @DisplayName("An empty array encoded as '[]' can be deserialized successfully.") + void testDeserializeEmpty() { + ArrayTypeHandler typeHandler = new ArrayTypeHandler<>( + new StringTypeHandler(), + TypeInfo.of(String.class) + ); + + var testData = new PersistedValueArray(List.of()); + + var result = typeHandler.deserialize(testData); + + assertThat(result).isPresent(); + assertThat(Array.getLength(result.get())).isEqualTo(0); + } } diff --git a/subsystems/TypeHandlerLibrary/src/test/java/org/terasology/persistence/typeHandling/coreTypes/CollectionTypeHandlerTest.java b/subsystems/TypeHandlerLibrary/src/test/java/org/terasology/persistence/typeHandling/coreTypes/CollectionTypeHandlerTest.java index 5e4b8f8d52f..875133dab45 100644 --- a/subsystems/TypeHandlerLibrary/src/test/java/org/terasology/persistence/typeHandling/coreTypes/CollectionTypeHandlerTest.java +++ b/subsystems/TypeHandlerLibrary/src/test/java/org/terasology/persistence/typeHandling/coreTypes/CollectionTypeHandlerTest.java @@ -6,18 +6,27 @@ import com.google.common.collect.Queues; import gnu.trove.list.TIntList; import gnu.trove.list.array.TIntArrayList; + +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.mockito.ArgumentMatcher; import org.mockito.stubbing.Answer; import org.terasology.persistence.typeHandling.PersistedData; import org.terasology.persistence.typeHandling.PersistedDataSerializer; import org.terasology.persistence.typeHandling.inMemory.arrays.PersistedIntegerArray; +import org.terasology.persistence.typeHandling.inMemory.arrays.PersistedValueArray; +import org.terasology.reflection.TypeInfo; import org.terasology.reflection.reflect.CollectionCopyConstructor; +import java.lang.reflect.Array; import java.util.Collection; import java.util.Collections; +import java.util.List; import java.util.Queue; +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth8.assertThat; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.mock; @@ -78,4 +87,20 @@ void testDeserialize() { verify(elementTypeHandler, times(intList.size())).deserialize(any()); } + + @Test + @DisplayName("An empty collection encoded as '[]' can be deserialized successfully.") + void testDeserializeEmpty() { + ArrayTypeHandler typeHandler = new ArrayTypeHandler<>( + new StringTypeHandler(), + TypeInfo.of(String.class) + ); + + var testData = new PersistedValueArray(List.of()); + + var result = typeHandler.deserialize(testData); + + assertThat(result).isPresent(); + assertThat(Array.getLength(result.get())).isEqualTo(0); + } } diff --git a/subsystems/TypeHandlerLibrary/src/test/java/org/terasology/persistence/typeHandling/coreTypes/GenericMapTypeHandlerTest.java b/subsystems/TypeHandlerLibrary/src/test/java/org/terasology/persistence/typeHandling/coreTypes/GenericMapTypeHandlerTest.java index 0cb9ec45bd6..54a52be6216 100644 --- a/subsystems/TypeHandlerLibrary/src/test/java/org/terasology/persistence/typeHandling/coreTypes/GenericMapTypeHandlerTest.java +++ b/subsystems/TypeHandlerLibrary/src/test/java/org/terasology/persistence/typeHandling/coreTypes/GenericMapTypeHandlerTest.java @@ -119,6 +119,14 @@ GenericMapTypeHandler.VALUE, new PersistedLong(TEST_VALUE) )) )); + /** + * JSON equivalent: + *

+     * [ ]
+     * 
+ */ + private final PersistedData testDataValidEmpty = new PersistedValueArray(List.of()); + @Test @DisplayName("Data with valid formatting can be deserialized successfully.") void testDeserialize() { @@ -197,6 +205,17 @@ void testDeserializeWithValidAndInvalidEntries() { assertThat(th.deserialize(testDataValidAndInvalidMix)).isEmpty(); } + @Test + @DisplayName("An empty map encoded as empty array '[]' can be deserialized successfully.") + void testDeserializeEmptyMap() { + var th = new GenericMapTypeHandler<>(new StringTypeHandler(), new LongTypeHandler()); + + var result = th.deserialize(testDataValidEmpty); + + assertThat(result).isPresent(); + assertThat(result.get()).isEmpty(); + } + /** Never returns a value. */ private static class UselessTypeHandler extends TypeHandler { @Override From 26d5b5237ba34b4b4c97d9f10469512432b14d90 Mon Sep 17 00:00:00 2001 From: Tobias Nett Date: Mon, 17 Jul 2023 21:13:56 +0200 Subject: [PATCH 024/100] refactor!: remove multi-world support (#5116) Remove code and abstractions related to mutli-word support during the "Advanced" game creation flow. Co-authored-by: Josephine Rueckert --- .idea/kotlinc.xml | 6 + .../layers/mainMenu/PreviewWorldScreen.java | 377 ------------------ .../layers/mainMenu/StartPlayingScreen.java | 26 +- .../layers/mainMenu/UniverseSetupScreen.java | 164 +------- .../mainMenu/WorldPreGenerationScreen.java | 99 ++--- .../nui/layers/mainMenu/WorldSetupScreen.java | 17 +- .../terasology/engine/assets/i18n/menu.lang | 2 +- .../engine/assets/i18n/menu_cs.lang | 5 +- .../engine/assets/i18n/menu_de.lang | 1 + .../engine/assets/i18n/menu_en.lang | 6 +- .../engine/assets/i18n/menu_fr.lang | 5 +- .../engine/assets/i18n/menu_ja.lang | 5 +- .../engine/assets/i18n/menu_sq.lang | 5 +- .../engine/assets/i18n/menu_uk.lang | 5 +- .../assets/ui/menu/previewWorldScreen.ui | 186 --------- .../assets/ui/menu/universeSetupScreen.ui | 81 +--- .../ui/menu/worldPreGenerationScreen.ui | 20 +- .../engine/assets/ui/worldSetupScreen.ui | 29 +- 18 files changed, 86 insertions(+), 953 deletions(-) create mode 100644 .idea/kotlinc.xml delete mode 100644 engine/src/main/java/org/terasology/engine/rendering/nui/layers/mainMenu/PreviewWorldScreen.java delete mode 100644 engine/src/main/resources/org/terasology/engine/assets/ui/menu/previewWorldScreen.ui diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml new file mode 100644 index 00000000000..7e340a776a6 --- /dev/null +++ b/.idea/kotlinc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/engine/src/main/java/org/terasology/engine/rendering/nui/layers/mainMenu/PreviewWorldScreen.java b/engine/src/main/java/org/terasology/engine/rendering/nui/layers/mainMenu/PreviewWorldScreen.java deleted file mode 100644 index 824afce8015..00000000000 --- a/engine/src/main/java/org/terasology/engine/rendering/nui/layers/mainMenu/PreviewWorldScreen.java +++ /dev/null @@ -1,377 +0,0 @@ -// Copyright 2021 The Terasology Foundation -// SPDX-License-Identifier: Apache-2.0 -package org.terasology.engine.rendering.nui.layers.mainMenu; - -import com.google.common.collect.Lists; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.terasology.engine.config.Config; -import org.terasology.engine.context.Context; -import org.terasology.engine.context.internal.ContextImpl; -import org.terasology.engine.core.SimpleUri; -import org.terasology.engine.core.bootstrap.EnvironmentSwitchHandler; -import org.terasology.engine.core.module.ModuleManager; -import org.terasology.engine.entitySystem.metadata.ComponentLibrary; -import org.terasology.engine.registry.CoreRegistry; -import org.terasology.engine.registry.In; -import org.terasology.engine.rendering.assets.texture.Texture; -import org.terasology.engine.rendering.assets.texture.TextureData; -import org.terasology.engine.rendering.nui.CoreScreenLayer; -import org.terasology.engine.rendering.nui.NUIManager; -import org.terasology.engine.rendering.nui.animation.MenuAnimationSystems; -import org.terasology.engine.rendering.nui.layers.mainMenu.preview.FacetLayerPreview; -import org.terasology.engine.rendering.nui.layers.mainMenu.preview.PreviewGenerator; -import org.terasology.engine.utilities.Assets; -import org.terasology.engine.world.generator.WorldConfigurator; -import org.terasology.engine.world.generator.WorldGenerator; -import org.terasology.engine.world.generator.internal.WorldGeneratorManager; -import org.terasology.engine.world.generator.plugin.TempWorldGeneratorPluginLibrary; -import org.terasology.engine.world.generator.plugin.WorldGeneratorPluginLibrary; -import org.terasology.engine.world.zones.Zone; -import org.terasology.gestalt.assets.ResourceUrn; -import org.terasology.gestalt.assets.module.ModuleAwareAssetTypeManager; -import org.terasology.gestalt.entitysystem.component.Component; -import org.terasology.gestalt.module.ModuleEnvironment; -import org.terasology.gestalt.module.dependencyresolution.DependencyResolver; -import org.terasology.gestalt.module.dependencyresolution.ResolutionResult; -import org.terasology.gestalt.module.exceptions.UnresolvedDependencyException; -import org.terasology.math.TeraMath; -import org.terasology.nui.WidgetUtil; -import org.terasology.nui.databinding.Binding; -import org.terasology.nui.layouts.PropertyLayout; -import org.terasology.nui.properties.OneOfProviderFactory; -import org.terasology.nui.properties.Property; -import org.terasology.nui.properties.PropertyOrdering; -import org.terasology.nui.properties.PropertyProvider; -import org.terasology.nui.widgets.UIButton; -import org.terasology.nui.widgets.UIDropdown; -import org.terasology.nui.widgets.UIImage; -import org.terasology.nui.widgets.UISlider; -import org.terasology.nui.widgets.UIText; -import org.terasology.reflection.metadata.FieldMetadata; -import org.terasology.reflection.reflect.ReflectFactory; - -import java.nio.ByteBuffer; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.Callable; -import java.util.stream.Collectors; - -/** - * Shows a preview of the generated world and provides some - * configuration options to tweak the generation process. - */ -public class PreviewWorldScreen extends CoreScreenLayer { - - public static final ResourceUrn ASSET_URI = new ResourceUrn("engine:previewWorldScreen"); - - private static final Logger logger = LoggerFactory.getLogger(PreviewWorldScreen.class); - - @In - private ModuleManager moduleManager; - - @In - private ModuleAwareAssetTypeManager assetTypeManager; - - @In - private WorldGeneratorManager worldGeneratorManager; - - @In - private Config config; - - @In - private Context context; - - private WorldGenerator worldGenerator; - - private UIImage previewImage; - private UISlider zoomSlider; - private UIDropdown zoneSelector; - private UIButton applyButton; - - private UIText seed; - - private PreviewGenerator previewGen; - - - private Context subContext; - private ModuleEnvironment environment; - - private Texture texture; - - private boolean triggerUpdate; - private String targetZone = "Surface"; - - public PreviewWorldScreen() { - } - - public void setEnvironment() throws Exception { - - // TODO: pass world gen and module list directly rather than using the config - SimpleUri worldGenUri = config.getWorldGeneration().getDefaultGenerator(); - - DependencyResolver resolver = new DependencyResolver(moduleManager.getRegistry()); - ResolutionResult result = resolver.resolve(config.getDefaultModSelection().listModules()); - if (result.isSuccess()) { - subContext = new ContextImpl(context); - CoreRegistry.setContext(subContext); - environment = moduleManager.loadEnvironment(result.getModules(), false); - subContext.put(WorldGeneratorPluginLibrary.class, new TempWorldGeneratorPluginLibrary(environment, subContext)); - EnvironmentSwitchHandler environmentSwitchHandler = context.get(EnvironmentSwitchHandler.class); - environmentSwitchHandler.handleSwitchToPreviewEnvironment(subContext, environment); - genTexture(); - - worldGenerator = WorldGeneratorManager.createWorldGenerator(worldGenUri, subContext, environment); - worldGenerator.setWorldSeed(seed.getText()); - - List previewZones = Lists.newArrayList(worldGenerator.getZones()) - .stream() - .filter(z -> !z.getPreviewLayers().isEmpty()) - .collect(Collectors.toList()); - if (previewZones.isEmpty()) { - zoneSelector.setVisible(false); - previewGen = new FacetLayerPreview(environment, worldGenerator); - } else { - zoneSelector.setVisible(true); - zoneSelector.setOptions(previewZones); - zoneSelector.setSelection(previewZones.get(0)); - } - - configureProperties(); - } else { - throw new UnresolvedDependencyException("Unable to resolve dependencies for " + worldGenUri); - } - } - - private void genTexture() { - int imgWidth = 384; - int imgHeight = 384; - ByteBuffer buffer = ByteBuffer.allocateDirect(imgWidth * imgHeight * Integer.BYTES); - ByteBuffer[] data = new ByteBuffer[]{buffer}; - ResourceUrn uri = new ResourceUrn("engine:terrainPreview"); - TextureData texData = new TextureData(imgWidth, imgHeight, data, Texture.WrapMode.CLAMP, Texture.FilterMode.LINEAR); - texture = Assets.generateAsset(uri, texData, Texture.class); - - previewImage = find("preview", UIImage.class); - previewImage.setImage(texture); - } - - @Override - public void update(float delta) { - super.update(delta); - - if (triggerUpdate) { - updatePreview(); - triggerUpdate = false; - } - } - - private void configureProperties() { - - PropertyLayout propLayout = find("properties", PropertyLayout.class); - propLayout.setOrdering(PropertyOrdering.byLabel()); - propLayout.clear(); - - WorldConfigurator worldConfig = worldGenerator.getConfigurator(); - - Map params = worldConfig.getProperties(); - - for (String key : params.keySet()) { - Class clazz = params.get(key).getClass(); - Component comp = config.getModuleConfig(worldGenerator.getUri(), key, clazz); - if (comp != null) { - worldConfig.setProperty(key, comp); // use the data from the config instead of defaults - } - } - - ComponentLibrary compLib = subContext.get(ComponentLibrary.class); - - for (String label : params.keySet()) { - - PropertyProvider provider = new PropertyProvider(context.get(ReflectFactory.class), context.get(OneOfProviderFactory.class)) { - @Override - protected Binding createTextBinding(Object target, FieldMetadata fieldMetadata) { - return new WorldConfigBinding<>(worldConfig, label, compLib, fieldMetadata); - } - - @Override - protected Binding createFloatBinding(Object target, FieldMetadata fieldMetadata) { - return new WorldConfigNumberBinding(worldConfig, label, compLib, fieldMetadata); - } - }; - - Component target = params.get(label); - List> properties = provider.createProperties(target); - propLayout.addProperties(label, properties); - } - } - - private void resetEnvironment() { - - CoreRegistry.setContext(context); - - if (environment != null) { - EnvironmentSwitchHandler environmentSwitchHandler = context.get(EnvironmentSwitchHandler.class); - environmentSwitchHandler.handleSwitchBackFromPreviewEnvironment(subContext); - environment.close(); - environment = null; - } - - previewGen.close(); - - WorldConfigurator worldConfig = worldGenerator.getConfigurator(); - - Map params = worldConfig.getProperties(); - if (params != null) { - config.setModuleConfigs(worldGenerator.getUri(), params); - } - } - - @Override - public void initialise() { - setAnimationSystem(MenuAnimationSystems.createDefaultSwipeAnimation()); - - zoomSlider = find("zoomSlider", UISlider.class); - if (zoomSlider != null) { - zoomSlider.setValue(2f); - } - - seed = find("seed", UIText.class); - - zoneSelector = find("zoneSelector", UIDropdown.class); - - applyButton = find("apply", UIButton.class); - if (applyButton != null) { - applyButton.subscribe(widget -> updatePreview()); - } - - WidgetUtil.trySubscribe(this, "close", w -> { - resetEnvironment(); - triggerBackAnimation(); - }); - } - - @Override - public boolean isLowerLayerVisible() { - return false; - } - - public void bindSeed(Binding binding) { - if (seed == null) { - // TODO: call initialize through NUIManager instead of onOpened() - seed = find("seed", UIText.class); - } - seed.bindText(binding); - } - - private void updatePreview() { - - final NUIManager manager = context.get(NUIManager.class); - final WaitPopup popup = manager.pushScreen(WaitPopup.ASSET_URI, WaitPopup.class); - popup.setMessage("Updating Preview", "Please wait ..."); - - ProgressListener progressListener = progress -> - popup.setMessage("Updating Preview", String.format("Please wait ... %d%%", (int) (progress * 100f))); - - Callable operation = () -> { - if (seed != null) { - worldGenerator.setWorldSeed(seed.getText()); - } - int zoom = TeraMath.floorToInt(zoomSlider.getValue()); - TextureData data = texture.getData(); - - if (zoneSelector.isVisible()) { - previewGen = zoneSelector.getSelection().preview(worldGenerator); - } - previewGen.render(data, zoom, progressListener); - - return data; - }; - - popup.onSuccess(texture::reload); - popup.startOperation(operation, true); - } - - /** - * Updates a world configurator through setProperty() whenever Binding#set() is called. - */ - private static class WorldConfigBinding implements Binding { - private final String label; - private final WorldConfigurator worldConfig; - private final FieldMetadata fieldMetadata; - private final ComponentLibrary compLib; - - protected WorldConfigBinding(WorldConfigurator config, String label, ComponentLibrary compLib, FieldMetadata fieldMetadata) { - this.worldConfig = config; - this.label = label; - this.compLib = compLib; - this.fieldMetadata = fieldMetadata; - } - - @Override - public T get() { - Component comp = worldConfig.getProperties().get(label); - return fieldMetadata.getValue(comp); - } - - @Override - public void set(T value) { - T old = get(); - - if (!Objects.equals(old, value)) { - cloneAndSet(label, value); - } - } - - private void cloneAndSet(String group, Object value) { - Component comp = worldConfig.getProperties().get(group); - Component clone = compLib.copy(comp); - fieldMetadata.setValue(clone, value); - - // notify the world generator about the new component - worldConfig.setProperty(label, clone); - } - } - - private static class WorldConfigNumberBinding implements Binding { - - private WorldConfigBinding binding; - - @SuppressWarnings("unchecked") - protected WorldConfigNumberBinding(WorldConfigurator config, String label, ComponentLibrary compLib, FieldMetadata field) { - Class type = field.getType(); - if (type == Integer.TYPE || type == Integer.class) { - this.binding = new WorldConfigBinding<>(config, label, compLib, - (FieldMetadata) field); - } else if (type == Float.TYPE || type == Float.class) { - this.binding = new WorldConfigBinding<>(config, label, compLib, - (FieldMetadata) field); - } - } - - @Override - public Float get() { - Number val = binding.get(); - if (val instanceof Float) { - // use boxed instance directly - return (Float) val; - } - // create a boxed instance otherwise - return val.floatValue(); - } - - @Override - @SuppressWarnings("unchecked") - public void set(Float value) { - Class type = binding.fieldMetadata.getType(); - if (type == Integer.TYPE || type == Integer.class) { - ((Binding) binding).set(value.intValue()); - } else if (type == Float.TYPE || type == Float.class) { - ((Binding) binding).set(value); - } - } - } -} - - diff --git a/engine/src/main/java/org/terasology/engine/rendering/nui/layers/mainMenu/StartPlayingScreen.java b/engine/src/main/java/org/terasology/engine/rendering/nui/layers/mainMenu/StartPlayingScreen.java index ce661fe073d..cf3708df4a2 100644 --- a/engine/src/main/java/org/terasology/engine/rendering/nui/layers/mainMenu/StartPlayingScreen.java +++ b/engine/src/main/java/org/terasology/engine/rendering/nui/layers/mainMenu/StartPlayingScreen.java @@ -7,7 +7,6 @@ import org.terasology.engine.context.Context; import org.terasology.engine.core.GameEngine; import org.terasology.engine.core.SimpleUri; -import org.terasology.engine.core.TerasologyConstants; import org.terasology.engine.core.modes.StateLoading; import org.terasology.engine.core.module.ModuleManager; import org.terasology.engine.game.GameManifest; @@ -19,15 +18,12 @@ import org.terasology.engine.rendering.nui.animation.MenuAnimationSystems; import org.terasology.engine.rendering.world.WorldSetupWrapper; import org.terasology.engine.world.internal.WorldInfo; -import org.terasology.engine.world.time.WorldTime; import org.terasology.gestalt.assets.ResourceUrn; import org.terasology.nui.Canvas; import org.terasology.nui.WidgetUtil; import org.terasology.nui.widgets.UIImage; import org.terasology.nui.widgets.UILabel; -import java.util.List; - public class StartPlayingScreen extends CoreScreenLayer { public static final ResourceUrn ASSET_URI = new ResourceUrn("engine:startPlayingScreen"); @@ -45,7 +41,6 @@ public class StartPlayingScreen extends CoreScreenLayer { private TranslationSystem translationSystem; private Texture texture; - private List worldSetupWrappers; private UniverseWrapper universeWrapper; private WorldSetupWrapper targetWorld; @@ -70,19 +65,8 @@ public void initialise() { SimpleUri uri; WorldInfo worldInfo; + //TODO: if we don't do that here, where do we do it? or does the world not show up in the game manifest? //gameManifest.addWorld(worldInfo); - int i = 0; - for (WorldSetupWrapper world : worldSetupWrappers) { - if (world != targetWorld) { - i++; - uri = world.getWorldGeneratorInfo().getUri(); - worldInfo = new WorldInfo(TerasologyConstants.MAIN_WORLD + i, world.getWorldName().toString(), - world.getWorldGenerator().getWorldSeed(), (long) (WorldTime.DAY_LENGTH * WorldTime.NOON_OFFSET), uri); - gameManifest.addWorld(worldInfo); - config.getUniverseConfig().addWorldManager(worldInfo); - } - - } gameEngine.changeState(new StateLoading(gameManifest, (universeWrapper.getLoadingAsServer()) ? NetworkMode.DEDICATED_SERVER @@ -99,20 +83,18 @@ public void onOpened() { UIImage previewImage = find("preview", UIImage.class); previewImage.setImage(texture); - UILabel subitle = find("subtitle", UILabel.class); - subitle.setText(translationSystem.translate("${engine:menu#start-playing}") + " in " + targetWorld.getWorldName().toString()); + UILabel subtitle = find("subtitle", UILabel.class); + subtitle.setText(translationSystem.translate("${engine:menu#start-playing}") + " in " + targetWorld.getWorldName().toString()); } /** * This method is called before the screen comes to the forefront to set the world in which the player is about to spawn. * - * @param worldSetupWrapperList The world in which the player is going to spawn. * @param targetWorldTexture The world texture generated in {@link WorldPreGenerationScreen} to be displayed on this screen. */ - public void setTargetWorld(List worldSetupWrapperList, WorldSetupWrapper spawnWorld, + public void setTargetWorld(WorldSetupWrapper spawnWorld, Texture targetWorldTexture, Context context) { texture = targetWorldTexture; - worldSetupWrappers = worldSetupWrapperList; universeWrapper = context.get(UniverseWrapper.class); targetWorld = spawnWorld; } diff --git a/engine/src/main/java/org/terasology/engine/rendering/nui/layers/mainMenu/UniverseSetupScreen.java b/engine/src/main/java/org/terasology/engine/rendering/nui/layers/mainMenu/UniverseSetupScreen.java index 9d6ec26d239..9ff3983524b 100644 --- a/engine/src/main/java/org/terasology/engine/rendering/nui/layers/mainMenu/UniverseSetupScreen.java +++ b/engine/src/main/java/org/terasology/engine/rendering/nui/layers/mainMenu/UniverseSetupScreen.java @@ -79,22 +79,18 @@ public class UniverseSetupScreen extends CoreScreenLayer { @In private Config config; - private List worlds = Lists.newArrayList(); private ModuleEnvironment environment; private ModuleAwareAssetTypeManager assetTypeManager; private Context context; - private int worldNumber; - private String selectedWorld = ""; - private int indexOfSelectedWorld; - private WorldSetupWrapper copyOfSelectedWorld; + private WorldSetupWrapper selectedWorld; @Override public void initialise() { setAnimationSystem(MenuAnimationSystems.createDefaultSwipeAnimation()); - final UIDropdownScrollable worldGenerator = find("worldGenerators", UIDropdownScrollable.class); - if (worldGenerator != null) { - worldGenerator.bindOptions(new ReadOnlyBinding>() { + final UIDropdownScrollable worldGenerators = find("worldGenerators", UIDropdownScrollable.class); + if (worldGenerators != null) { + worldGenerators.bindOptions(new ReadOnlyBinding>() { @Override public List get() { // grab all the module names and their dependencies @@ -102,7 +98,10 @@ public List get() { final Set enabledModuleNames = new HashSet<>(getAllEnabledModuleNames()); final List result = Lists.newArrayList(); for (WorldGeneratorInfo option : worldGeneratorManager.getWorldGenerators()) { - if (enabledModuleNames.contains(option.getUri().getModuleName())) { + //TODO: There should not be a reference from the engine to some module. + // The engine must be agnostic to what modules may do. + if (enabledModuleNames.contains(option.getUri().getModuleName()) + && !option.getUri().toString().equals("CoreWorlds:heightMap")) { result.add(option); } } @@ -110,8 +109,8 @@ public List get() { return result; } }); - worldGenerator.setVisibleOptions(3); - worldGenerator.bindSelection(new Binding() { + worldGenerators.setVisibleOptions(3); + worldGenerators.bindSelection(new Binding() { @Override public WorldGeneratorInfo get() { // get the default generator from the config. This is likely to have a user triggered selection. @@ -138,7 +137,7 @@ public void set(WorldGeneratorInfo value) { } } }); - worldGenerator.setOptionRenderer(new StringTextRenderer() { + worldGenerators.setOptionRenderer(new StringTextRenderer() { @Override public String getString(WorldGeneratorInfo value) { if (value != null) { @@ -148,56 +147,16 @@ public String getString(WorldGeneratorInfo value) { } }); } - final UIDropdownScrollable worldsDropdown = find("worlds", UIDropdownScrollable.class); - worldsDropdown.bindSelection(new Binding() { - @Override - public String get() { - return selectedWorld; - } - - @Override - public void set(String value) { - selectedWorld = value; - indexOfSelectedWorld = findIndex(worlds, selectedWorld); - } - }); WidgetUtil.trySubscribe(this, "close", button -> triggerBackAnimation() ); - WidgetUtil.trySubscribe(this, "worldConfig", button -> { - final WorldSetupScreen worldSetupScreen = getManager().createScreen(WorldSetupScreen.ASSET_URI, WorldSetupScreen.class); - try { - if (!worlds.isEmpty() || !selectedWorld.isEmpty()) { - worldSetupScreen.setWorld(context, findWorldByName(), worldsDropdown); - triggerForwardAnimation(worldSetupScreen); - } else { - getManager().pushScreen(MessagePopup.ASSET_URI, MessagePopup.class) - .setMessage("Worlds List Empty!", "No world found to configure."); - } - } catch (UnresolvedWorldGeneratorException e) { - logger.error("Can't configure the world! due to {}", e.getMessage()); - } - }); - - WidgetUtil.trySubscribe(this, "addGenerator", button -> { - //TODO: there should not be a reference from the engine to some module - the engine must be agnostic to what - // modules may do - if (worldGenerator.getSelection().getUri().toString().equals("CoreWorlds:heightMap")) { - getManager().pushScreen(MessagePopup.ASSET_URI, MessagePopup.class) - .setMessage("HeightMap not supported", - "HeightMap is not supported for advanced setup right now, a game template will be introduced soon."); - } else { - addNewWorld(worldGenerator.getSelection()); - worldsDropdown.setOptions(worldNames()); - } - }); - WidgetUtil.trySubscribe(this, "continue", button -> { final WorldPreGenerationScreen worldPreGenerationScreen = getManager().createScreen(WorldPreGenerationScreen.ASSET_URI, WorldPreGenerationScreen.class); - if (!worlds.isEmpty()) { + addNewWorld(worldGenerators.getSelection()); + if (selectedWorld != null) { final WaitPopup loadPopup = getManager().pushScreen(WaitPopup.ASSET_URI, WaitPopup.class); loadPopup.setMessage("Loading", "please wait ..."); loadPopup.onSuccess(result -> { @@ -231,14 +190,7 @@ public void set(String value) { public void onOpened() { super.onOpened(); - worlds.clear(); - worldNumber = 0; - final UIDropdownScrollable worldsDropdown = find("worlds", UIDropdownScrollable.class); - if (worldsDropdown != null) { - worldsDropdown.setOptions(worldNames()); - } - selectedWorld = ""; - indexOfSelectedWorld = findIndex(worlds, selectedWorld); + selectedWorld = null; } private Set getAllEnabledModuleNames() { @@ -262,23 +214,6 @@ private void recursivelyAddModuleDependencies(Set modules, Name moduleName } } - /** - * returns true if 'name' matches (case-insensitive) with another world already present - * @param name The world name to be checked - */ - public boolean worldNameMatchesAnother(String name) { - boolean taken = false; - - for (WorldSetupWrapper worldTaken: worlds) { - if (worldTaken.getWorldName().toString().equalsIgnoreCase(name)) { - taken = true; - break; - } - } - - return taken; - } - /** * Called whenever the user decides to add a new world. * @param worldGeneratorInfo The {@link WorldGeneratorInfo} object for the new world. @@ -286,24 +221,7 @@ public boolean worldNameMatchesAnother(String name) { private void addNewWorld(WorldGeneratorInfo worldGeneratorInfo) { String selectedWorldName = worldGeneratorInfo.getDisplayName(); - while (worldNameMatchesAnother(selectedWorldName + "-" + worldNumber)) { - ++worldNumber; - } - - selectedWorld = worldGeneratorInfo.getDisplayName() + '-' + worldNumber; - worlds.add(new WorldSetupWrapper(new Name(worldGeneratorInfo.getDisplayName() + '-' + worldNumber), worldGeneratorInfo)); - indexOfSelectedWorld = findIndex(worlds, selectedWorld); - ++worldNumber; - } - - /** - * This method refreshes the worlds drop-down menu when world name is changed and updates variable selectedWorld. - * @param worldsDropdown the drop-down to work on - */ - public void refreshWorldDropdown(UIDropdownScrollable worldsDropdown) { - worldsDropdown.setOptions(worldNames()); - copyOfSelectedWorld = worlds.get(indexOfSelectedWorld); - selectedWorld = copyOfSelectedWorld.getWorldName().toString(); + selectedWorld = new WorldSetupWrapper(new Name(selectedWorldName), worldGeneratorInfo); } /** @@ -342,23 +260,6 @@ public void setEnvironment(UniverseWrapper wrapper) { } } - /** - * Looks for the index of a selected world from the given list. - * @param worldsList the list to search - * @param worldName the name of the world to find - * @return the found index value or -1 if not found - */ - private int findIndex(List worldsList, String worldName) { - for (int i = 0; i < worldsList.size(); i++) { - WorldSetupWrapper currentWorldFromList = worldsList.get(i); - Name customName = currentWorldFromList.getWorldName(); - if (customName.toString().equals(worldName)) { - return i; - } - } - return -1; - } - private void initAssets() { ModuleEnvironment environment = context.get(ModuleManager.class).getEnvironment(); @@ -380,40 +281,9 @@ private void initAssets() { } /** - * Create a list of the names of the world, so that they can be displayed as simple String - * in the drop-down. - * @return A list of world names encoded as a String - */ - public List worldNames() { - final List worldNamesList = Lists.newArrayList(); - for (WorldSetupWrapper world : worlds) { - worldNamesList.add(world.getWorldName().toString()); - } - return worldNamesList; - } - - /** - * This method takes the name of the selected world as String and return the corresponding - * WorldSetupWrapper object. - * @return {@link WorldSetupWrapper} object. - */ - public WorldSetupWrapper findWorldByName() { - for (WorldSetupWrapper world : worlds) { - if (world.getWorldName().toString().equals(selectedWorld)) { - return world; - } - } - return null; - } - - public List getWorldsList() { - return worlds; - } - - /** - * @return the selcted world in the drop-down. + * @return the selected world in the drop-down. */ - public String getSelectedWorld() { + public WorldSetupWrapper getSelectedWorld() { return selectedWorld; } diff --git a/engine/src/main/java/org/terasology/engine/rendering/nui/layers/mainMenu/WorldPreGenerationScreen.java b/engine/src/main/java/org/terasology/engine/rendering/nui/layers/mainMenu/WorldPreGenerationScreen.java index c5c5bc3e0bd..42e443bd941 100644 --- a/engine/src/main/java/org/terasology/engine/rendering/nui/layers/mainMenu/WorldPreGenerationScreen.java +++ b/engine/src/main/java/org/terasology/engine/rendering/nui/layers/mainMenu/WorldPreGenerationScreen.java @@ -20,10 +20,10 @@ import org.terasology.gestalt.naming.Name; import org.terasology.nui.WidgetUtil; import org.terasology.nui.databinding.Binding; -import org.terasology.nui.widgets.UIDropdownScrollable; import org.terasology.nui.widgets.UIImage; import org.terasology.nui.widgets.UISlider; import org.terasology.nui.widgets.UISliderOnChangeTriggeredListener; +import org.terasology.nui.widgets.UIText; import org.terasology.engine.registry.In; import org.terasology.engine.rendering.nui.CoreScreenLayer; import org.terasology.engine.rendering.nui.NUIManager; @@ -63,9 +63,7 @@ public class WorldPreGenerationScreen extends CoreScreenLayer implements UISlide private UIImage previewImage; private Context context; private PreviewGenerator previewGen; - private List worldList; - private String selectedWorld; - private List worldNames; + private WorldSetupWrapper selectedWorld; private int seedNumber; private UISlider zoomSlider; @@ -81,15 +79,12 @@ public void setEnvironment(Context subContext) throws UnresolvedWorldGeneratorEx context = subContext; environment = context.get(ModuleEnvironment.class); context.put(WorldGeneratorPluginLibrary.class, new TempWorldGeneratorPluginLibrary(environment, context)); - worldList = context.get(UniverseSetupScreen.class).getWorldsList(); selectedWorld = context.get(UniverseSetupScreen.class).getSelectedWorld(); - worldNames = context.get(UniverseSetupScreen.class).worldNames(); - setWorldGenerators(); + ensureWorldGeneratorIsSet(); + selectedWorld.getWorldGenerator().setWorldSeed(createSeed(selectedWorld.getWorldName().toString())); - worldGenerator = findWorldByName(selectedWorld).getWorldGenerator(); - final UIDropdownScrollable worldsDropdown = find("worlds", UIDropdownScrollable.class); - worldsDropdown.setOptions(worldNames); + worldGenerator = selectedWorld.getWorldGenerator(); genTexture(); List previewZones = Lists.newArrayList(worldGenerator.getZones()) @@ -111,52 +106,36 @@ public void initialise() { zoomSlider.setUiSliderOnChangeTriggeredListener(this); } - - final UIDropdownScrollable worldsDropdown = find("worlds", UIDropdownScrollable.class); - worldsDropdown.bindSelection(new Binding() { + final UIText worldName = find("worldName", UIText.class); + worldName.bindText(new Binding() { @Override public String get() { - return selectedWorld; + return selectedWorld.getWorldName().toString(); } @Override public void set(String value) { - selectedWorld = value; - try { - if (findWorldByName(selectedWorld).getWorldGenerator() == null) { - worldGenerator = WorldGeneratorManager.createWorldGenerator(findWorldByName(selectedWorld) - .getWorldGeneratorInfo().getUri(), context, environment); - findWorldByName(selectedWorld).setWorldGenerator(worldGenerator); - } else { - worldGenerator = findWorldByName(selectedWorld).getWorldGenerator(); - } - if (worldGenerator.getWorldSeed() == null) { - worldGenerator.setWorldSeed(createSeed(selectedWorld)); - } - previewGen = new FacetLayerPreview(environment, worldGenerator); - updatePreview(); - } catch (UnresolvedWorldGeneratorException e) { - e.printStackTrace(); - } + // no-op + // field should be read-only } }); WidgetUtil.trySubscribe(this, "reRoll", button -> { - worldGenerator.setWorldSeed(createSeed(selectedWorld)); + worldGenerator.setWorldSeed(createSeed(selectedWorld.getWorldName().toString())); updatePreview(); }); StartPlayingScreen startPlayingScreen = getManager().createScreen(StartPlayingScreen.ASSET_URI, StartPlayingScreen.class); WidgetUtil.trySubscribe(this, "continue", button -> { - startPlayingScreen.setTargetWorld(worldList, findWorldByName(selectedWorld), texture, context); + startPlayingScreen.setTargetWorld(selectedWorld, texture, context); triggerForwardAnimation(startPlayingScreen); }); WorldSetupScreen worldSetupScreen = getManager().createScreen(WorldSetupScreen.ASSET_URI, WorldSetupScreen.class); WidgetUtil.trySubscribe(this, "config", button -> { try { - if (!selectedWorld.isEmpty()) { - worldSetupScreen.setWorld(context, findWorldByName(selectedWorld), worldsDropdown); + if (!selectedWorld.getWorldName().isEmpty()) { + worldSetupScreen.setWorld(context, selectedWorld); triggerForwardAnimation(worldSetupScreen); } else { getManager().pushScreen(MessagePopup.ASSET_URI, MessagePopup.class) @@ -168,10 +147,6 @@ public void set(String value) { }); WidgetUtil.trySubscribe(this, "close", button -> { - final UniverseSetupScreen universeSetupScreen = - getManager().createScreen(UniverseSetupScreen.ASSET_URI, UniverseSetupScreen.class); - UIDropdownScrollable worldsDropdownOfUniverse = universeSetupScreen.find("worlds", UIDropdownScrollable.class); - universeSetupScreen.refreshWorldDropdown(worldsDropdownOfUniverse); triggerBackAnimation(); }); @@ -185,15 +160,15 @@ public void onOpened() { super.onOpened(); try { - if (findWorldByName(selectedWorld).getWorldGenerator() == null) { - worldGenerator = WorldGeneratorManager.createWorldGenerator(findWorldByName(selectedWorld) + if (selectedWorld.getWorldGenerator() == null) { + worldGenerator = WorldGeneratorManager.createWorldGenerator(selectedWorld .getWorldGeneratorInfo().getUri(), context, environment); - findWorldByName(selectedWorld).setWorldGenerator(worldGenerator); + selectedWorld.setWorldGenerator(worldGenerator); } else { - worldGenerator = findWorldByName(selectedWorld).getWorldGenerator(); + worldGenerator = selectedWorld.getWorldGenerator(); } if (worldGenerator.getWorldSeed().isEmpty()) { - worldGenerator.setWorldSeed(createSeed(selectedWorld)); + worldGenerator.setWorldSeed(createSeed(selectedWorld.getWorldName().toString())); } previewGen = new FacetLayerPreview(environment, worldGenerator); updatePreview(); @@ -202,12 +177,13 @@ public void onOpened() { } } + //TODO: this does not actually only set it when configure is called from WorldPreGenerationScreen, but also if called from UniverseSetupScreen /** * Set seletedWorld when configure from WorldPreGenerationScreen * @param newNameToSet */ public void setName(Name newNameToSet) { - selectedWorld = newNameToSet.toString(); + selectedWorld.setWorldName(newNameToSet); } /** @@ -252,21 +228,6 @@ private void updatePreview() { popup.startOperation(operation, true); } - /** - * This method takes the name of the world selected in the worldsDropdown - * as String and return the corresponding WorldSetupWrapper object. - * - * @return {@link WorldSetupWrapper} object of the selected world. - */ - private WorldSetupWrapper findWorldByName(String searchWorld) { - for (WorldSetupWrapper world : worldList) { - if (world.getWorldName().toString().equals(searchWorld)) { - return world; - } - } - return null; - } - /** * Creates a unique world seed by appending the world name with an incrementing number, on top of the universe seed. * @@ -278,17 +239,15 @@ private String createSeed(String world) { return seed + world + seedNumber++; } - private void setWorldGenerators() { - for (WorldSetupWrapper worldSetupWrapper : worldList) { - if (worldSetupWrapper.getWorldGenerator() == null) { - try { - worldSetupWrapper.setWorldGenerator(WorldGeneratorManager.createWorldGenerator(findWorldByName( - worldSetupWrapper.getWorldName().toString()).getWorldGeneratorInfo().getUri(), context, environment)); - } catch (UnresolvedWorldGeneratorException e) { - e.printStackTrace(); - } + private void ensureWorldGeneratorIsSet() { + if (selectedWorld.getWorldGenerator() == null) { + try { + selectedWorld.setWorldGenerator(WorldGeneratorManager.createWorldGenerator( + selectedWorld.getWorldGeneratorInfo().getUri(), context, environment)); + } catch (UnresolvedWorldGeneratorException e) { + //TODO: this will likely fail at game creation time later-on due to lack of world generator - don't just ignore this + e.printStackTrace(); } - worldSetupWrapper.getWorldGenerator().setWorldSeed(createSeed(worldSetupWrapper.getWorldName().toString())); } } diff --git a/engine/src/main/java/org/terasology/engine/rendering/nui/layers/mainMenu/WorldSetupScreen.java b/engine/src/main/java/org/terasology/engine/rendering/nui/layers/mainMenu/WorldSetupScreen.java index 7bb95ee29e5..bdf90f8ff82 100644 --- a/engine/src/main/java/org/terasology/engine/rendering/nui/layers/mainMenu/WorldSetupScreen.java +++ b/engine/src/main/java/org/terasology/engine/rendering/nui/layers/mainMenu/WorldSetupScreen.java @@ -28,7 +28,6 @@ import org.terasology.nui.properties.Property; import org.terasology.nui.properties.PropertyOrdering; import org.terasology.nui.properties.PropertyProvider; -import org.terasology.nui.widgets.UIDropdownScrollable; import org.terasology.nui.widgets.UILabel; import org.terasology.nui.widgets.UIText; import org.terasology.reflection.metadata.FieldMetadata; @@ -60,17 +59,12 @@ public class WorldSetupScreen extends CoreScreenLayer { private Context context; private WorldConfigurator oldWorldConfig; private Name newWorldName; - private UIDropdownScrollable worldsDropdown; @Override public void initialise() { setAnimationSystem(MenuAnimationSystems.createDefaultSwipeAnimation()); - WidgetUtil.trySubscribe(this, "close", button -> { - final UniverseSetupScreen universeSetupScreen = - getManager().createScreen(UniverseSetupScreen.ASSET_URI, UniverseSetupScreen.class); - final WorldPreGenerationScreen worldPreGenerationScreen = - getManager().createScreen(WorldPreGenerationScreen.ASSET_URI, WorldPreGenerationScreen.class); + WidgetUtil.trySubscribe(this, "close", button -> { UIText customWorldName = find("customisedWorldName", UIText.class); boolean goBack = false; @@ -83,10 +77,6 @@ public void initialise() { } else if (customWorldName.getText().equalsIgnoreCase(world.getWorldName().toString())) { //same name as before: go back to universe setup goBack = true; - } else if (universeSetupScreen.worldNameMatchesAnother(customWorldName.getText())) { - //if same name is already used, inform user with a popup - getManager().pushScreen(MessagePopup.ASSET_URI, MessagePopup.class) - .setMessage("Name Already Used!", "Please use a different name for this world"); } else { //no match found: go back to universe setup goBack = true; @@ -95,8 +85,6 @@ public void initialise() { if (goBack) { newWorldName = new Name(customWorldName.getText()); world.setWorldName(newWorldName); - universeSetupScreen.refreshWorldDropdown(worldsDropdown); - worldPreGenerationScreen.setName(newWorldName); triggerBackAnimation(); } }); @@ -129,11 +117,10 @@ public void onOpened() { * @param worldSelected the world whose configurations are to be changed. * @throws UnresolvedWorldGeneratorException */ - public void setWorld(Context subContext, WorldSetupWrapper worldSelected, UIDropdownScrollable dropDown) + public void setWorld(Context subContext, WorldSetupWrapper worldSelected) throws UnresolvedWorldGeneratorException { world = worldSelected; context = subContext; - worldsDropdown = dropDown; SimpleUri worldGenUri = worldSelected.getWorldGeneratorInfo().getUri(); environment = context.get(ModuleEnvironment.class); context.put(WorldGeneratorPluginLibrary.class, new TempWorldGeneratorPluginLibrary(environment, context)); diff --git a/engine/src/main/resources/org/terasology/engine/assets/i18n/menu.lang b/engine/src/main/resources/org/terasology/engine/assets/i18n/menu.lang index e94decf18e5..56a1c4ae60d 100644 --- a/engine/src/main/resources/org/terasology/engine/assets/i18n/menu.lang +++ b/engine/src/main/resources/org/terasology/engine/assets/i18n/menu.lang @@ -254,7 +254,6 @@ "motion-blur": "motion-blur", "mouse-sensitivity": "mouse-sensitivity", "movement-dead-zone": "movement-dead-zone", - "multi-world-warning": "multi-world-warning", "multiplayer-identities": "multiplayer-identities", "music-volume": "music-volume", "new-binding": "new-binding", @@ -440,6 +439,7 @@ "world-config-preview": "world-config-preview", "world-configuration": "world-configuration", "world-generator": "world-generator", + "world-name": "world-name", "world-pre-generation": "world-pre-generation", "world-seed": "world-seed", "world-setup": "world-setup", diff --git a/engine/src/main/resources/org/terasology/engine/assets/i18n/menu_cs.lang b/engine/src/main/resources/org/terasology/engine/assets/i18n/menu_cs.lang index 7179a7508de..261cc7c397f 100644 --- a/engine/src/main/resources/org/terasology/engine/assets/i18n/menu_cs.lang +++ b/engine/src/main/resources/org/terasology/engine/assets/i18n/menu_cs.lang @@ -250,7 +250,6 @@ "motion-blur": "Rozmazání pohybem", "mouse-sensitivity": "Citlivost myši", "movement-dead-zone": "Mrtvá zóna pohybové osy", - "multi-world-warning": "VAROVÁNÍ: Multi-world není dokončen, pouze finálně vybraný svět bude k dispozici!", "music-volume": "Hlasitost hudby", "new-binding": "Nové přiřazení", "new-game-title": "Nová hra", @@ -290,7 +289,7 @@ "player-settings-identities-import": "Importovat...", "player-settings-title": "Nastavení hráče", "please-wait": "Prosím počkej...", - "pregeneration-description": "Vyber svět ze seznamu k náhledu. Klikni na Přegenerovat k náhodnému výběru nového seedu nebo na Nastavit pro detailní úpravu světa", + "pregeneration-description": "Klikni na Přegenerovat k náhodnému výběru nového seedu nebo na Nastavit pro detailní úpravu světa", "preview-world-title": "Náhled světa...", "preview-zoom-factor": "Přiblížení", "previous-toolbar-item": "Předchozí předmět v toolbaru", @@ -396,7 +395,7 @@ "this-language-English": "Czech", "this-language-native": "Česky", "universe-setup": "Nastavení Vesmíru", - "universe-setup-description": "Vyber generátor světa a přidej ho. Poté vyber a nastav světy z čerstvě vyplněného seznamu světů.", + "universe-setup-description": "Vyber generátor světa.", "update-module": "Aktualizovat", "time-progression-during-pre-generation": "Postup času během předgenerace", "validation-username-max-length": "Jméno nesmí být delší než 100 znaků", diff --git a/engine/src/main/resources/org/terasology/engine/assets/i18n/menu_de.lang b/engine/src/main/resources/org/terasology/engine/assets/i18n/menu_de.lang index 80f66a0dfeb..69ae53f7dad 100644 --- a/engine/src/main/resources/org/terasology/engine/assets/i18n/menu_de.lang +++ b/engine/src/main/resources/org/terasology/engine/assets/i18n/menu_de.lang @@ -248,5 +248,6 @@ "widget-selection-prompt": "Bitte wähle ein Widget aus:", "widget-selection-title": "Widget-Auswahl", "world-config-preview": "Details ...", + "world-name": "Weltname", "world-seed": "Seed" } diff --git a/engine/src/main/resources/org/terasology/engine/assets/i18n/menu_en.lang b/engine/src/main/resources/org/terasology/engine/assets/i18n/menu_en.lang index ef11b1edc13..53540ee2557 100644 --- a/engine/src/main/resources/org/terasology/engine/assets/i18n/menu_en.lang +++ b/engine/src/main/resources/org/terasology/engine/assets/i18n/menu_en.lang @@ -259,7 +259,6 @@ "motion-blur": "Motion Blur", "mouse-sensitivity": "Mouse Sensitivity", "movement-dead-zone": "Movement Axis Dead Zone", - "multi-world-warning": "WARNING: Multi-world is not completed, only the final selected world will be available!", "multiplayer-identities": "Multiplayer identities:", "music-volume": "Music Volume", "new-binding": "New binding", @@ -299,7 +298,7 @@ "player-settings-identities-import": "Import...", "player-settings-title": "Player Settings", "please-wait": "Please wait...", - "pregeneration-description": "Select a world in the drop-down to see it previewed. Click Re-Roll to randomly pick a new seed or Configure to manually tweak the world further", + "pregeneration-description": "Click Re-Roll to randomly pick a new seed or Configure to manually tweak the world further", "preview-world-title": "Preview World...", "preview-zoom-factor": "Zoom", "previous-toolbar-item": "Previous Toolbar Item", @@ -411,7 +410,7 @@ "this-language-English": "English", "this-language-native": "English", "universe-setup": "Universe Setup", - "universe-setup-description": "Pick a world generator and add it. Then select and configure worlds from the freshly filled Worlds dropdown.", + "universe-setup-description": "Pick a world generator.", "update-module": "Update", "time-progression-during-pre-generation": "Time progression during pre-generation", "validation-username-max-length": "Username can't be longer then 100 chars", @@ -457,6 +456,7 @@ "world-config-preview": "Details...", "world-configuration": "World Configuration", "world-generator": "World Generator", + "world-name": "World Name", "world-pre-generation": "World Pre-generation", "world-seed": "Seed", "world-setup": "World Setup", diff --git a/engine/src/main/resources/org/terasology/engine/assets/i18n/menu_fr.lang b/engine/src/main/resources/org/terasology/engine/assets/i18n/menu_fr.lang index e1fd06ffb72..26977592153 100644 --- a/engine/src/main/resources/org/terasology/engine/assets/i18n/menu_fr.lang +++ b/engine/src/main/resources/org/terasology/engine/assets/i18n/menu_fr.lang @@ -249,7 +249,6 @@ "motion-blur": "Flou cinétique", "mouse-sensitivity": "Sensibilité de la souris", "movement-dead-zone": "Mouvement dans l'axe de la zone morte", - "multi-world-warning": "AVERTISSEMENT: le multi-monde n'est pas terminé, seul le dernier monde sélectionné sera disponible !", "music-volume": "Volume de la musique", "new-binding": "Nouveau racourcie", "new-game-title": "Nouvelle partie", @@ -288,7 +287,7 @@ "player-settings-identities-import": "Import...", "player-settings-title": "Paramètres du joueur", "please-wait": "Veuillez patienter...", - "pregeneration-description": "Sélectionnez un monde dans le menu déroulant pour le voir en aperçu. Cliquez sur Jet-de-dés pour choisir au hasard une nouvelle graine ou sur Configurer pour modifier manuellement le monde", + "pregeneration-description": "Cliquez sur Jet-de-dés pour choisir au hasard une nouvelle graine ou sur Configurer pour modifier manuellement le monde", "preview-world-title": "Prévisualisation du monde...", "preview-zoom-factor": "Zoom", "previous-toolbar-item": "Barre d'outil précédente", @@ -392,7 +391,7 @@ "this-language-English": "French", "this-language-native": "Français", "universe-setup": "Configuration de l'univers", - "universe-setup-description": "Choisissez un générateur de monde et ajoutez-le. Ensuite, sélectionnez et configurez des mondes dans la liste déroulante des mondes fraîchement remplie.", + "universe-setup-description": "Choisissez un générateur de monde.", "update-module": "Mise à jour", "time-progression-during-pre-generation": "Progression de temps pendant la pré-génération", "validation-username-max-length": "Le nom d'utilisateur ne peut pas être plus long que 100 caractères", diff --git a/engine/src/main/resources/org/terasology/engine/assets/i18n/menu_ja.lang b/engine/src/main/resources/org/terasology/engine/assets/i18n/menu_ja.lang index f9b1735a1eb..44b70300e80 100644 --- a/engine/src/main/resources/org/terasology/engine/assets/i18n/menu_ja.lang +++ b/engine/src/main/resources/org/terasology/engine/assets/i18n/menu_ja.lang @@ -252,7 +252,6 @@ "motion-blur": "モーション・ブラー", "mouse-sensitivity": "マウスの感度", "movement-dead-zone": "移動する座標は行けません", - "multi-world-warning": "警告: 多重ワールドは終わってません,最後に選択されたワールドのみ利用可能です!", "music-volume": "音量", "new-binding": "新しいバインディング", "new-game-title": "ニュー・ゲーム", @@ -291,7 +290,7 @@ "player-settings-identities-import": "インポート...", "player-settings-title": "プレイヤーのセッティング", "please-wait": "しばらくお待ちください", - "pregeneration-description": "ドロップ・ダウンから観たいワールドを選べ。リロールをクリックすることで新しいシードをランダムに選びます。または手動でワールドを微設定することもできます", + "pregeneration-description": "「再ロール」をクリックして、新しいシードをランダムに選択します。 または、世界を手動で調整することもできます", "preview-world-title": "ワールドのプレビュー...", "preview-zoom-factor": "ズーム", "previous-toolbar-item": "前のツールバーの項目", @@ -396,7 +395,7 @@ "this-language-English": "Japanese", "this-language-native": "日本語", "universe-setup": "全体の設定", - "universe-setup-description": 新しいワールドをドロップダウンから選んで設定したとき、ワールドジェネレータを選択し追加してください。", + "universe-setup-description": "ワールドジェネレーターを選択します。", "update-module": "アップデート", "time-progression-during-pre-generation": "事前生成中の時間経過", "validation-username-max-length": "ユーザーネームは100文字以上長くできません", diff --git a/engine/src/main/resources/org/terasology/engine/assets/i18n/menu_sq.lang b/engine/src/main/resources/org/terasology/engine/assets/i18n/menu_sq.lang index 07af70b742d..97cd3142b8b 100644 --- a/engine/src/main/resources/org/terasology/engine/assets/i18n/menu_sq.lang +++ b/engine/src/main/resources/org/terasology/engine/assets/i18n/menu_sq.lang @@ -249,7 +249,6 @@ "motion-blur": "Lëvizja e turbullimit", "mouse-sensitivity": "Ndjeshmëri e mausit", "movement-dead-zone": "Movement Axis Dead Zone", - "multi-world-warning": "WARNING: Multi-world is not completed, only the final selected world will be available!", "music-volume": "Niveli i Muzikës", "new-binding": "Lidhja e re", "new-game-title": "Lojë e Re", @@ -288,7 +287,7 @@ "player-settings-identities-import": "Import...", "player-settings-title": "Rrëgullimet e Lojtarit", "please-wait": "Te Lutem Prit...", - "pregeneration-description": "Select a world in the drop-down to see it previewed. Click Re-Roll to randomly pick a new seed or Configure to manually tweak the world further", + "pregeneration-description": "Click Re-Roll to randomly pick a new seed or Configure to manually tweak the world further", "preview-world-title": "Për të Parë Botën...", "preview-zoom-factor": "Zoom", "previous-toolbar-item": "Objekti i mëparshëm i toolbarit", @@ -392,7 +391,7 @@ "this-language-English": "Albanian", "this-language-native": "Shqip", "universe-setup": "Rrëgullimi i universit ", - "universe-setup-description": "Zgjidh një gjenerator të botës dhe shtoje atë. Pastaj zgjidhni dhe konfiguroni botët nga rishtarja e mbushur Botët dropdown.", + "universe-setup-description": "Zgjidhni një gjenerator botëror.", "update-module": "Update", "time-progression-during-pre-generation": "Progresi i kohës gjatë pre-lindja", "validation-username-max-length": "Emri nuk mun të jetë më i gjatë se 100 shenja", diff --git a/engine/src/main/resources/org/terasology/engine/assets/i18n/menu_uk.lang b/engine/src/main/resources/org/terasology/engine/assets/i18n/menu_uk.lang index a2e9d0fe347..ece87ce1d36 100644 --- a/engine/src/main/resources/org/terasology/engine/assets/i18n/menu_uk.lang +++ b/engine/src/main/resources/org/terasology/engine/assets/i18n/menu_uk.lang @@ -259,7 +259,6 @@ "motion-blur": "Розмиття", "mouse-sensitivity": "Чутливість мишки", "movement-dead-zone": "Мертва зона осі руху", - "multi-world-warning": "ПОПЕРЕДЖЕННЯ: мульти-світи незакінчені, буде доступний тільки останній обраний світ!", "multiplayer-identities": "Облікові записи:", "music-volume": "Гучність музики", "new-binding": "Нова прив'язка", @@ -299,7 +298,7 @@ "player-settings-identities-import": "Імпортувати...", "player-settings-title": "Налаштування гравця", "please-wait": "Будь ласка, зачекайте...", - "pregeneration-description": "Виберіть світ зі списку для попереднього перегляду. Натисніть 'Перегенерувати', щоб довільно обрати нове зерно, або 'Конфігурація', щоб власноруч редагувати світ.", + "pregeneration-description": "Натисніть 'Перегенерувати', щоб довільно обрати нове зерно, або 'Конфігурація', щоб власноруч редагувати світ.", "preview-world-title": "Попередній перегляд...", "preview-zoom-factor": "Масштаб", "previous-toolbar-item": "Попередній інструмент", @@ -411,7 +410,7 @@ "this-language-English": "Ukrainian", "this-language-native": "Українська", "universe-setup": "Налаштування світу", - "universe-setup-description": "Виберіть генератор світу і додайте його - потім виберіть та налаштуйте світи з новоствореного списку.", + "universe-setup-description": "Виберіть генератор світу.", "update-module": "Оновити", "time-progression-during-pre-generation": "Прогрес часу під час попереднього генерування", "validation-username-max-length": "Логін має бути коротшим за 100 символів", diff --git a/engine/src/main/resources/org/terasology/engine/assets/ui/menu/previewWorldScreen.ui b/engine/src/main/resources/org/terasology/engine/assets/ui/menu/previewWorldScreen.ui deleted file mode 100644 index 8f2ac83136c..00000000000 --- a/engine/src/main/resources/org/terasology/engine/assets/ui/menu/previewWorldScreen.ui +++ /dev/null @@ -1,186 +0,0 @@ -{ - "type": "engine:previewWorldScreen", - "skin": "engine:mainMenu", - "contents": { - "type": "relativeLayout", - "contents": [ - { - "type": "UILabel", - "id": "title", - "family": "title", - "text": "${engine:menu#preview-world-title}", - "layoutInfo": { - "height": 48, - "position-horizontal-center": {}, - "position-top": { - "target": "TOP", - "offset": 48 - } - } - }, - { - "type": "UIBox", - "id": "container", - "layoutInfo": { - "width": 720, - "position-horizontal-center": {}, - "position-top": { - "target": "BOTTOM", - "offset": 16, - "widget": "title" - }, - "position-bottom": { - "target": "TOP", - "widget": "close", - "offset": 16 - } - } - }, - { - "type": "ColumnLayout", - "columns": 2, - "verticalSpacing": 16, - "horizontalSpacing": 8, - "column-widths": [0.53, 0.47], - "layoutInfo": { - "width": 704, - "position-horizontal-center": {}, - "position-top": { - "target": "TOP", - "widget": "container", - "offset": 8 - }, - "position-bottom": { - "target": "TOP", - "widget": "close", - "offset": 24 - } - }, - "contents": [ - { - "type": "UIImage", - "skin": "framed_image", - "id": "preview" - }, - { - "type": "RelativeLayout", - "family": "description", - "contents": [ - { - "type": "UILabel", - "text": "${engine:menu#preview-zoom-factor}:", - "id": "zoomLabel", - "layoutInfo": { - "use-content-height": true, - "position-horizontal-center": {}, - "position-top": { - "target": "TOP" - } - } - }, - { - "type": "UISlider", - "id": "zoomSlider", - "minimum": 1.0, - "range": 7.0, - "increment": 1.0, - "precision": 0, - "layoutInfo": { - "use-content-height": true, - "position-horizontal-center": {}, - "position-top": { - "target": "BOTTOM", - "widget": "zoomLabel" - } - } - }, - { - "type": "UIDropdown", - "id": "zoneSelector", - "layoutInfo": { - "use-content-height": true, - "position-top": { - "target": "BOTTOM", - "widget": "zoomSlider", - "offset": 8 - } - } - }, - { - "type": "UILabel", - "id": "seedLabel", - "text": "${engine:menu#world-seed}:", - "layoutInfo": { - "use-content-height": true, - "position-top": { - "target": "BOTTOM", - "widget": "zoneSelector", - "offset": 8 - } - } - }, - { - "type": "UIText", - "id": "seed", - "layoutInfo": { - "use-content-height": true, - "position-top": { - "target": "BOTTOM", - "widget": "seedLabel" - } - } - }, - { - "type": "ScrollableArea", - "content": { - "type": "propertyLayout", - "id": "properties", - "rowConstraints": "[min]" - }, - "layoutInfo": { - "position-horizontal-center": {}, - "position-top": { - "target": "BOTTOM", - "offset": 8, - "widget": "seed" - }, - "position-bottom": { - "target": "TOP", - "offset": 8, - "widget": "apply" - } - } - }, - { - "type": "UIButton", - "text": "${engine:menu#create-preview}", - "id": "apply", - "layoutInfo": { - "height": 32, - "position-bottom": { - "target": "BOTTOM", - "offset": 4 - } - } - } - ] - } - ] - }, - { - "type": "UIButton", - "text": "${engine:menu#back}", - "id": "close", - "layoutInfo": { - "width": 128, - "height": 32, - "position-horizontal-center": {}, - "position-bottom": { - "target": "BOTTOM", - "offset": 48 - } - } - } - ] - } -} diff --git a/engine/src/main/resources/org/terasology/engine/assets/ui/menu/universeSetupScreen.ui b/engine/src/main/resources/org/terasology/engine/assets/ui/menu/universeSetupScreen.ui index 667f3db9f56..c4290c916a7 100644 --- a/engine/src/main/resources/org/terasology/engine/assets/ui/menu/universeSetupScreen.ui +++ b/engine/src/main/resources/org/terasology/engine/assets/ui/menu/universeSetupScreen.ui @@ -18,24 +18,9 @@ } } }, - { - "type": "UILabel", - "id": "warning", - "text": "${engine:menu#multi-world-warning}", - "family": "warning", - "layoutInfo": { - "height": 12, - "position-horizontal-center": {}, - "position-bottom": { - "target": "TOP", - "widget": "mainBox", - "offset": 36 - } - } - }, { "type": "UIBox", - "id":"mainBox", + "id": "mainBox", "content": { "type": "ColumnLayout", "columns": 1, @@ -59,30 +44,8 @@ "type": "UIDropdownScrollable", "id": "worldGenerators", "layoutInfo": { - "relativeWidth": 0.50 + "relativeWidth": 1.00 } - }, - { - "type": "UISpace", - "size": [ - 1, - 8 - ] - }, - { - "type": "UIButton", - "id": "addGenerator", - "text": "${engine:menu#add}", - "layoutInfo": { - "relativeWidth": 0.30 - } - }, - { - "type": "UISpace", - "size": [ - 1, - 8 - ] } ] }, @@ -92,46 +55,6 @@ 1, 8 ] - }, - { - "type": "UILabel", - "text": "${engine:menu#game-worlds}:", - "family": "left-label" - }, - { - "type": "RowLayout", - "horizontalSpacing": 4, - "contents": [ - { - "type": "UIDropdownScrollable", - "id": "worlds", - "layoutInfo": { - "relativeWidth": 0.50 - } - }, - { - "type": "UISpace", - "size": [ - 1, - 8 - ] - }, - { - "type": "UIButton", - "id": "worldConfig", - "text": "${engine:menu#config}", - "layoutInfo": { - "relativeWidth": 0.30 - } - }, - { - "type": "UISpace", - "size": [ - 1, - 8 - ] - } - ] } ] }, diff --git a/engine/src/main/resources/org/terasology/engine/assets/ui/menu/worldPreGenerationScreen.ui b/engine/src/main/resources/org/terasology/engine/assets/ui/menu/worldPreGenerationScreen.ui index 74aa550f5fe..600dd45cdf9 100644 --- a/engine/src/main/resources/org/terasology/engine/assets/ui/menu/worldPreGenerationScreen.ui +++ b/engine/src/main/resources/org/terasology/engine/assets/ui/menu/worldPreGenerationScreen.ui @@ -41,7 +41,10 @@ "id": "coreLayout", "columns": 2, "horizontalSpacing": 8, - "column-widths": [0.53, 0.47], + "column-widths": [ + 0.53, + 0.47 + ], "layoutInfo": { "width": 704, "use-content-height": true, @@ -133,16 +136,11 @@ } }, { - "type": "UIDropdownScrollable", - "id": "worlds", - "layoutInfo": { - "position-horizontal-center": {}, - "position-top": { - "target": "BOTTOM", - "widget": "worldGenLabel", - "offset": 0 - } - } + "type": "engine:UIText", + "id": "worldName", + "readOnly": true, + "enabled": false, + "layoutInfo": {} }, { "type": "RowLayout", diff --git a/engine/src/main/resources/org/terasology/engine/assets/ui/worldSetupScreen.ui b/engine/src/main/resources/org/terasology/engine/assets/ui/worldSetupScreen.ui index 427be1445be..0260c70e9e3 100644 --- a/engine/src/main/resources/org/terasology/engine/assets/ui/worldSetupScreen.ui +++ b/engine/src/main/resources/org/terasology/engine/assets/ui/worldSetupScreen.ui @@ -60,29 +60,6 @@ "type": "RelativeLayout", "family": "description", "contents": [ - { - "type": "RowLayout", - "id": "shape", - "layoutInfo": { - "use-content-height": true, - "position-horizontal-center": {}, - "position-top": { - "target": "TOP" - } - }, - "contents": [ - { - "type": "UILabel", - "text": "World Shape:", - "enabled": false - }, - { - "type": "UIDropdownScrollable", - "id": "worlds", - "enabled": false - } - ] - }, { "type": "RowLayout", "id": "customNameWorld", @@ -90,15 +67,13 @@ "use-content-height": true, "position-horizontal-center": {}, "position-top": { - "target": "BOTTOM", - "offset": 8, - "widget": "shape" + "target": "TOP" } }, "contents": [ { "type": "UILabel", - "text": "World Name" + "text": "${engine:menu#world-name}" }, { "type": "UIText", From 9b863ddde475a30b262999f45b4e10b5acd5d144 Mon Sep 17 00:00:00 2001 From: jdrueckert Date: Thu, 20 Jul 2023 23:05:42 +0200 Subject: [PATCH 025/100] refactor: merge StartPlaying (spawn preview) screen into WorldPreGeneration screen (#5118) Co-authored-by: Tobias Nett --- .../layers/mainMenu/StartPlayingScreen.java | 111 ------------- .../mainMenu/WorldPreGenerationScreen.java | 64 +++++--- .../assets/ui/menu/startPlayingScreen.ui | 153 ------------------ .../ui/menu/worldPreGenerationScreen.ui | 21 +-- 4 files changed, 42 insertions(+), 307 deletions(-) delete mode 100644 engine/src/main/java/org/terasology/engine/rendering/nui/layers/mainMenu/StartPlayingScreen.java delete mode 100644 engine/src/main/resources/org/terasology/engine/assets/ui/menu/startPlayingScreen.ui diff --git a/engine/src/main/java/org/terasology/engine/rendering/nui/layers/mainMenu/StartPlayingScreen.java b/engine/src/main/java/org/terasology/engine/rendering/nui/layers/mainMenu/StartPlayingScreen.java deleted file mode 100644 index cf3708df4a2..00000000000 --- a/engine/src/main/java/org/terasology/engine/rendering/nui/layers/mainMenu/StartPlayingScreen.java +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright 2022 The Terasology Foundation -// SPDX-License-Identifier: Apache-2.0 -package org.terasology.engine.rendering.nui.layers.mainMenu; - -import org.joml.Vector2i; -import org.terasology.engine.config.Config; -import org.terasology.engine.context.Context; -import org.terasology.engine.core.GameEngine; -import org.terasology.engine.core.SimpleUri; -import org.terasology.engine.core.modes.StateLoading; -import org.terasology.engine.core.module.ModuleManager; -import org.terasology.engine.game.GameManifest; -import org.terasology.engine.i18n.TranslationSystem; -import org.terasology.engine.network.NetworkMode; -import org.terasology.engine.registry.In; -import org.terasology.engine.rendering.assets.texture.Texture; -import org.terasology.engine.rendering.nui.CoreScreenLayer; -import org.terasology.engine.rendering.nui.animation.MenuAnimationSystems; -import org.terasology.engine.rendering.world.WorldSetupWrapper; -import org.terasology.engine.world.internal.WorldInfo; -import org.terasology.gestalt.assets.ResourceUrn; -import org.terasology.nui.Canvas; -import org.terasology.nui.WidgetUtil; -import org.terasology.nui.widgets.UIImage; -import org.terasology.nui.widgets.UILabel; - -public class StartPlayingScreen extends CoreScreenLayer { - - public static final ResourceUrn ASSET_URI = new ResourceUrn("engine:startPlayingScreen"); - - @In - private ModuleManager moduleManager; - - @In - private Config config; - - @In - private GameEngine gameEngine; - - @In - private TranslationSystem translationSystem; - - private Texture texture; - private UniverseWrapper universeWrapper; - private WorldSetupWrapper targetWorld; - - @Override - public void initialise() { - setAnimationSystem(MenuAnimationSystems.createDefaultSwipeAnimation()); - - WidgetUtil.trySubscribe(this, "close", button -> - triggerBackAnimation() - ); - - WidgetUtil.trySubscribe(this, "play", button -> { - universeWrapper.setTargetWorld(targetWorld); - final GameManifest gameManifest = GameManifestProvider.createGameManifest(universeWrapper, moduleManager, config); - if (gameManifest != null) { - gameEngine.changeState(new StateLoading(gameManifest, (universeWrapper.getLoadingAsServer()) - ? NetworkMode.DEDICATED_SERVER - : NetworkMode.NONE)); - } else { - getManager().createScreen(MessagePopup.ASSET_URI, MessagePopup.class).setMessage("Error", "Can't create new game!"); - } - - SimpleUri uri; - WorldInfo worldInfo; - //TODO: if we don't do that here, where do we do it? or does the world not show up in the game manifest? - //gameManifest.addWorld(worldInfo); - - gameEngine.changeState(new StateLoading(gameManifest, (universeWrapper.getLoadingAsServer()) - ? NetworkMode.DEDICATED_SERVER - : NetworkMode.NONE)); - }); - - WidgetUtil.trySubscribe(this, "mainMenu", button -> getManager().pushScreen("engine:mainMenuScreen")); - } - - @Override - public void onOpened() { - super.onOpened(); - - UIImage previewImage = find("preview", UIImage.class); - previewImage.setImage(texture); - - UILabel subtitle = find("subtitle", UILabel.class); - subtitle.setText(translationSystem.translate("${engine:menu#start-playing}") + " in " + targetWorld.getWorldName().toString()); - } - - /** - * This method is called before the screen comes to the forefront to set the world in which the player is about to spawn. - * - * @param targetWorldTexture The world texture generated in {@link WorldPreGenerationScreen} to be displayed on this screen. - */ - public void setTargetWorld(WorldSetupWrapper spawnWorld, - Texture targetWorldTexture, Context context) { - texture = targetWorldTexture; - universeWrapper = context.get(UniverseWrapper.class); - targetWorld = spawnWorld; - } - - @Override - public boolean isLowerLayerVisible() { - return false; - } - - @Override - public Vector2i getPreferredContentSize(Canvas canvas, Vector2i vector2i) { - return vector2i; - } -} diff --git a/engine/src/main/java/org/terasology/engine/rendering/nui/layers/mainMenu/WorldPreGenerationScreen.java b/engine/src/main/java/org/terasology/engine/rendering/nui/layers/mainMenu/WorldPreGenerationScreen.java index 42e443bd941..1a09cc29585 100644 --- a/engine/src/main/java/org/terasology/engine/rendering/nui/layers/mainMenu/WorldPreGenerationScreen.java +++ b/engine/src/main/java/org/terasology/engine/rendering/nui/layers/mainMenu/WorldPreGenerationScreen.java @@ -3,37 +3,41 @@ package org.terasology.engine.rendering.nui.layers.mainMenu; import com.google.common.collect.Lists; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.terasology.engine.core.module.ModuleManager; -import org.terasology.gestalt.assets.ResourceUrn; +import org.joml.Vector2i; import org.terasology.engine.config.Config; import org.terasology.engine.context.Context; +import org.terasology.engine.core.GameEngine; +import org.terasology.engine.core.modes.StateLoading; +import org.terasology.engine.core.module.ModuleManager; +import org.terasology.engine.game.GameManifest; +import org.terasology.engine.network.NetworkMode; +import org.terasology.engine.registry.In; import org.terasology.engine.rendering.assets.texture.Texture; import org.terasology.engine.rendering.assets.texture.TextureData; +import org.terasology.engine.rendering.nui.CoreScreenLayer; +import org.terasology.engine.rendering.nui.NUIManager; import org.terasology.engine.rendering.nui.animation.MenuAnimationSystems; import org.terasology.engine.rendering.nui.layers.mainMenu.preview.FacetLayerPreview; import org.terasology.engine.rendering.nui.layers.mainMenu.preview.PreviewGenerator; import org.terasology.engine.rendering.world.WorldSetupWrapper; -import org.terasology.math.TeraMath; +import org.terasology.engine.utilities.Assets; +import org.terasology.engine.world.generator.UnresolvedWorldGeneratorException; +import org.terasology.engine.world.generator.WorldGenerator; +import org.terasology.engine.world.generator.internal.WorldGeneratorManager; +import org.terasology.engine.world.generator.plugin.TempWorldGeneratorPluginLibrary; +import org.terasology.engine.world.generator.plugin.WorldGeneratorPluginLibrary; +import org.terasology.engine.world.zones.Zone; +import org.terasology.gestalt.assets.ResourceUrn; import org.terasology.gestalt.module.ModuleEnvironment; import org.terasology.gestalt.naming.Name; +import org.terasology.math.TeraMath; +import org.terasology.nui.Canvas; import org.terasology.nui.WidgetUtil; import org.terasology.nui.databinding.Binding; import org.terasology.nui.widgets.UIImage; import org.terasology.nui.widgets.UISlider; import org.terasology.nui.widgets.UISliderOnChangeTriggeredListener; import org.terasology.nui.widgets.UIText; -import org.terasology.engine.registry.In; -import org.terasology.engine.rendering.nui.CoreScreenLayer; -import org.terasology.engine.rendering.nui.NUIManager; -import org.terasology.engine.utilities.Assets; -import org.terasology.engine.world.generator.UnresolvedWorldGeneratorException; -import org.terasology.engine.world.generator.WorldGenerator; -import org.terasology.engine.world.generator.internal.WorldGeneratorManager; -import org.terasology.engine.world.generator.plugin.TempWorldGeneratorPluginLibrary; -import org.terasology.engine.world.generator.plugin.WorldGeneratorPluginLibrary; -import org.terasology.engine.world.zones.Zone; import java.nio.ByteBuffer; import java.util.List; @@ -49,20 +53,22 @@ public class WorldPreGenerationScreen extends CoreScreenLayer implements UISlide public static final ResourceUrn ASSET_URI = new ResourceUrn("engine:worldPreGenerationScreen"); - private static final Logger logger = LoggerFactory.getLogger(WorldPreGenerationScreen.class); - @In private ModuleManager moduleManager; @In private Config config; + @In + private GameEngine gameEngine; + private ModuleEnvironment environment; private WorldGenerator worldGenerator; private Texture texture; private UIImage previewImage; private Context context; private PreviewGenerator previewGen; + private UniverseWrapper universeWrapper; private WorldSetupWrapper selectedWorld; private int seedNumber; private UISlider zoomSlider; @@ -80,6 +86,7 @@ public void setEnvironment(Context subContext) throws UnresolvedWorldGeneratorEx environment = context.get(ModuleEnvironment.class); context.put(WorldGeneratorPluginLibrary.class, new TempWorldGeneratorPluginLibrary(environment, context)); selectedWorld = context.get(UniverseSetupScreen.class).getSelectedWorld(); + universeWrapper = context.get(UniverseWrapper.class); ensureWorldGeneratorIsSet(); selectedWorld.getWorldGenerator().setWorldSeed(createSeed(selectedWorld.getWorldName().toString())); @@ -125,12 +132,6 @@ public void set(String value) { updatePreview(); }); - StartPlayingScreen startPlayingScreen = getManager().createScreen(StartPlayingScreen.ASSET_URI, StartPlayingScreen.class); - WidgetUtil.trySubscribe(this, "continue", button -> { - startPlayingScreen.setTargetWorld(selectedWorld, texture, context); - triggerForwardAnimation(startPlayingScreen); - }); - WorldSetupScreen worldSetupScreen = getManager().createScreen(WorldSetupScreen.ASSET_URI, WorldSetupScreen.class); WidgetUtil.trySubscribe(this, "config", button -> { try { @@ -150,6 +151,18 @@ public void set(String value) { triggerBackAnimation(); }); + WidgetUtil.trySubscribe(this, "play", button -> { + universeWrapper.setTargetWorld(selectedWorld); + final GameManifest gameManifest = GameManifestProvider.createGameManifest(universeWrapper, moduleManager, config); + if (gameManifest != null) { + gameEngine.changeState(new StateLoading(gameManifest, (universeWrapper.getLoadingAsServer()) + ? NetworkMode.DEDICATED_SERVER + : NetworkMode.NONE)); + } else { + getManager().createScreen(MessagePopup.ASSET_URI, MessagePopup.class).setMessage("Error", "Can't create new game!"); + } + }); + WidgetUtil.trySubscribe(this, "mainMenu", button -> { getManager().pushScreen("engine:mainMenuScreen"); }); @@ -260,4 +273,9 @@ public void onSliderValueChanged(float val) { public boolean isLowerLayerVisible() { return false; } + + @Override + public Vector2i getPreferredContentSize(Canvas canvas, Vector2i vector2i) { + return vector2i; + } } diff --git a/engine/src/main/resources/org/terasology/engine/assets/ui/menu/startPlayingScreen.ui b/engine/src/main/resources/org/terasology/engine/assets/ui/menu/startPlayingScreen.ui deleted file mode 100644 index 86d0f44435c..00000000000 --- a/engine/src/main/resources/org/terasology/engine/assets/ui/menu/startPlayingScreen.ui +++ /dev/null @@ -1,153 +0,0 @@ -{ - "type": "engine:StartPlayingScreen", - "skin": "engine:mainMenu", - "contents": { - "type": "relativeLayout", - "contents": [ - { - "type": "UILabel", - "id": "title", - "family": "title", - "text": "${engine:menu#start-playing}", - "layoutInfo": { - "height": 48, - "position-horizontal-center": {}, - "position-top": { - "target": "TOP", - "offset": 48 - } - } - }, - { - "type": "UILabel", - "id": "subtitle", - "layoutInfo": { - "use-content-height": true, - "use-content-width": true, - "position-horizontal-center": {}, - "position-top": { - "target": "BOTTOM", - "widget": "title", - "offset": 32 - } - } - }, - { - "type": "UIBox", - "id": "container", - "layoutInfo": { - "position-horizontal-center": {}, - "width": 392, - "position-top": { - "target": "TOP", - "offset": -8, - "widget": "coreLayout" - }, - "position-bottom": { - "target": "BOTTOM", - "offset": -8, - "widget": "coreLayout" - } - } - }, - { - "type": "ColumnLayout", - "id": "coreLayout", - "columns": 1, - "horizontalSpacing": 8, - "layoutInfo": { - "width": 384, - "use-content-height": true, - "position-horizontal-center": {}, - "position-vertical-center": {} - }, - "contents": [ - { - "type": "UIImage", - "skin": "framed_image", - "id": "preview" - } - ] - }, - { - "type": "UIButton", - "text": "${engine:menu#return-pregeneration}", - "id": "close", - "layoutInfo": { - "width": 120, - "height": 80, - "position-vertical-center": { - "target": "CENTER", - "widget": "container" - }, - "position-right": { - "target": "LEFT", - "widget": "container", - "offset": 16 - } - } - }, - { - "type": "RowLayout", - "id": "actionsRow", - "horizontalSpacing": 32, - "contents": [ - { - "type": "UIButton", - "text": "${engine:menu#return-main-menu}", - "id": "mainMenu" - }, - { - "type": "UIButton", - "text": "${engine:menu#start-playing}", - "id": "play" - } - ], - "layoutInfo": { - "width": 350, - "height": 80, - "position-horizontal-center": {}, - "position-top": { - "target": "BOTTOM", - "widget": "container", - "offset": 16 - } - } - }, - { - "type": "UIImage", - "image": "engine:happyGooey", - "layoutInfo": { - "width": 87, - "height": 40, - "position-left": { - "target": "RIGHT", - "widget": "actionsRow", - "offset": 8 - }, - "position-vertical-center": { - "target": "CENTER", - "widget": "actionsRow" - } - } - }, - { - "type": "UIImage", - "image": "engine:angryGooey", - "layoutInfo": { - "width": 87, - "height": 40, - "position-right": { - "target": "LEFT", - "widget": "actionsRow", - "offset": 8 - }, - "position-vertical-center": { - "target": "CENTER", - "widget": "actionsRow" - } - } - } - ] - } -} \ No newline at end of file diff --git a/engine/src/main/resources/org/terasology/engine/assets/ui/menu/worldPreGenerationScreen.ui b/engine/src/main/resources/org/terasology/engine/assets/ui/menu/worldPreGenerationScreen.ui index 600dd45cdf9..4fb56d6823e 100644 --- a/engine/src/main/resources/org/terasology/engine/assets/ui/menu/worldPreGenerationScreen.ui +++ b/engine/src/main/resources/org/terasology/engine/assets/ui/menu/worldPreGenerationScreen.ui @@ -176,8 +176,7 @@ { "type": "UIButton", "text": "${engine:menu#start-playing}", - "id": "play", - "enabled": false + "id": "play" } ], "layoutInfo": { @@ -209,24 +208,6 @@ } } }, - { - "type": "UIButton", - "text": "${engine:menu#continue-spawn-preview}", - "id": "continue", - "layoutInfo": { - "width": 120, - "height": 80, - "position-vertical-center": { - "target": "CENTER", - "widget": "container" - }, - "position-left": { - "target": "RIGHT", - "widget": "container", - "offset": 16 - } - } - }, { "type": "UIImage", "image": "engine:happyGooey", From e33518e3a04a901de4796aa0cf68c8614bcc26cb Mon Sep 17 00:00:00 2001 From: Tobias Nett Date: Mon, 31 Jul 2023 20:13:39 +0200 Subject: [PATCH 026/100] doc(facade/PC): add README (#5123) Co-authored-by: BenjaminAmos <24301287+BenjaminAmos@users.noreply.github.com> Co-authored-by: jdrueckert --- facades/PC/README.md | 100 +++++ facades/PC/docs/pc-facade-overview.drawio.svg | 416 ++++++++++++++++++ 2 files changed, 516 insertions(+) create mode 100644 facades/PC/README.md create mode 100644 facades/PC/docs/pc-facade-overview.drawio.svg diff --git a/facades/PC/README.md b/facades/PC/README.md new file mode 100644 index 00000000000..b2ebdabf3f7 --- /dev/null +++ b/facades/PC/README.md @@ -0,0 +1,100 @@ +# PC Facade + +The _PC Facade_ is the front-facing layer for running _Terasology_ on the PC. + +## Usage + +Most users won't interact directly with the PC Facade, but instead use the [Terasology Launcher](https://github.com/MovingBlocks/TerasologyLauncher) to start the game. + +When starting distributions of Terasology manually, users will interact with the command-line interface ([CLI](#cli)). + +Developers will most often use the PC Facade via [Gradle](#gradle) or via a run configuration in their IDE. + +### CLI + +The PC Facade provides a command-line interface (CLI) for configuring Terasology. +See the usage help (`./Terasology --help`) for a full overview. +For implementing the CLI, we make use of the [PicoCLI](https://picocli.info/) framework for Java command-line applications. + +```sh +./Terasology --help + +Usage: terasology [-h] [--[no-]crash-report] [--create-last-game] [--headless] [--load-last-game] [--permissive-security] [--[no-]save-games] [--[no-]sound] + [--[no-]splash] [--homedir=] [--max-data-size=] [--oom-score=] [--override-default-config=] + [--server-port=] + + --[no-]crash-report Enable crash reporting + --create-last-game Recreates the world of the latest game with a new save file on startup + -h, /h, -?, /?, -help, /help, --help + Show help + --headless Start headless (no graphics) + --homedir= Path to home directory + --load-last-game Load the latest game on startup + --max-data-size= + Set maximum process data size [Linux only] + --oom-score= Adjust out-of-memory score [Linux only] + --override-default-config= + Override default config + --permissive-security + --[no-]save-games Enable new save games + --server-port= + Change the server port + --[no-]sound Enable sound + --[no-]splash Enable splash screen + +For details, see + https://github.com/MovingBlocks/Terasology/wiki/Advanced-Options + +Alternatively use our standalone Launcher from + https://github.com/MovingBlocks/TerasologyLauncher/releases +``` + +### Gradle + +The PC Facade provides a couple of Gradle tasks for starting the game on the PC. +These "Terasology run tasks" offer various default configurations, for instance, to enable debugging or start in _headless mode_. + +``` +Terasology run tasks +-------------------- +debug - Run 'Terasology' to play the game as a standard PC application (in debug mode) +game - Run 'Terasology' to play the game as a standard PC application +permissiveNatives - Run 'Terasology' with security set to permissive and natives loading a second way (for KComputers) +profile - Run 'Terasology' to play the game as a standard PC application (with Java FlightRecorder profiling) +server - Starts a headless multiplayer server with data stored in [project-root]/terasology-server +``` + +To pass additional arguments to the PC Facade when running via Gradle, use the `--args` option (see the Gradle [Application Plugin](https://docs.gradle.org/6.9.1/userguide/application_plugin.html) documentation). +For instance, to print the usage help, run + +```sh +gradlew game --args='--help' +``` + +## Architecture + +The main entry point for the PC Facade is `Terasology#main`. +Starting from there, the game engine is initialized and started according to the user inputs. + + +

+ +Architectural overview of the PC Facade entry point. +

+ +The execution flow is as follows: + +1. A `TerasologyEngineBuilder` is instantiated to prepare the game engine. + +1. Additional subsystems are collected via the builder. This creates and adds subsystems that are specific for running Terasology on a PC. The facade also chooses respective subsystem implementations depending on whether the game is run in _headless mode_. For instance, for a headless server it would choose `HeadlessGraphics` instead of `LwjglGraphics`. + +1. The builder is used to create an instance of `TerasologyEngine` which is subsequently initialized (`TerasologyEngine#initialize`, not to be confused with `GameEngine#initializeRun`). This triggers initialization of the engine for subsystems and asset management. This does not start the game loop yet. + +1. Depending on the arguments provided by the user, the facade creates an appropriate `GameState` + - `HeadlessSetup` if the game is started in _headless mode_ + - `Loading` if a game should directly be loaded (either by loading a save game or by creating a new one) + - `MainMenu` otherwise + +1. This state is passed to `TerasologyEngine#run` to start the game loop with the given state. + + diff --git a/facades/PC/docs/pc-facade-overview.drawio.svg b/facades/PC/docs/pc-facade-overview.drawio.svg new file mode 100644 index 00000000000..f736de8af5a --- /dev/null +++ b/facades/PC/docs/pc-facade-overview.drawio.svg @@ -0,0 +1,416 @@ + + + + + + + +
+
+
+ subsystems +
+
+
+
+ + subsystems + +
+
+ + + + +
+
+
+ engine +
+
+
+
+ + engine + +
+
+ + + + +
+
+
+ facades/PC +
+
+
+
+ + facades/PC + +
+
+ + + + + + + +
+
+
+ creates +
+
+
+
+ + creates + +
+
+ + + + + +
+
+
+ creates +
+
+
+
+ + creates + +
+
+ + + + + +
+
+
+ initialize() +
+
+
+
+ + initialize() + +
+
+ + + + + +
+
+
+ run(state) +
+
+
+
+ + run(state) + +
+
+ + + + + +
+
+
+ creates +
+
+
+
+ + creates + +
+
+ + + + Terasology + + + + + + + + +
+
+
+ creates +
+
+
+
+ + creates + +
+
+ + + + Terasology + + + EngineBuilder + + + + + + Terasology + + + Engine + + + + + + + + +
+
+
+ collect subsystems +
+
+
+
+ + collect subsy... + +
+
+ + + + +
+
+
+ org.terasology.engine.core.subsystem +
+
+
+
+ + org.terasology.engine.core.subsystem + +
+
+ + + + Graphics + + + + + + Audio + + + + + + Timer + + + + + + Input + + + + + + + +
+
+
+ some variants for headless modes exist +
+
+
+
+ + some variants... + +
+
+ + + + + Binds + + + + + + DiscordRPC + + + + + + Hibernation + + + + + + GameState + + + + + + HeadlessSetup + + + + + + MainMenu + + + + + + Loading + + + + + + +
+
+
+ + 1 + +
+
+
+
+ + 1 + +
+
+ + + + +
+
+
+ + 2 + +
+
+
+
+ + 2 + +
+
+ + + + +
+
+
+ + 4 + +
+
+
+
+ + 4 + +
+
+ + + + +
+
+
+ + 5 + +
+
+
+
+ + 5 + +
+
+ + + + + +
+
+
+ + 3 + +
+
+
+
+ + 3 + +
+
+
+ + + + + Text is not SVG - cannot display + + + +
\ No newline at end of file From 36591e16a4b5f120be8460013d69300ffbb48ec1 Mon Sep 17 00:00:00 2001 From: BenjaminAmos <24301287+BenjaminAmos@users.noreply.github.com> Date: Sun, 13 Aug 2023 19:00:30 +0100 Subject: [PATCH 027/100] feat: fix ExecutorCompletionService workaround in ChunkProcessingPipeline (#5128) --- .../ChunkExecutorCompletionService.java | 126 ++++++++++++++++++ .../pipeline/ChunkProcessingPipeline.java | 60 +-------- .../world/chunks/pipeline/PositionFuture.java | 50 ++----- 3 files changed, 143 insertions(+), 93 deletions(-) create mode 100644 engine/src/main/java/org/terasology/engine/world/chunks/pipeline/ChunkExecutorCompletionService.java diff --git a/engine/src/main/java/org/terasology/engine/world/chunks/pipeline/ChunkExecutorCompletionService.java b/engine/src/main/java/org/terasology/engine/world/chunks/pipeline/ChunkExecutorCompletionService.java new file mode 100644 index 00000000000..01a6ef7357c --- /dev/null +++ b/engine/src/main/java/org/terasology/engine/world/chunks/pipeline/ChunkExecutorCompletionService.java @@ -0,0 +1,126 @@ +// Copyright 2023 The Terasology Foundation +// SPDX-License-Identifier: Apache-2.0 + +package org.terasology.engine.world.chunks.pipeline; + +import org.joml.Vector3i; +import org.joml.Vector3ic; +import org.terasology.engine.world.chunks.Chunk; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletionService; +import java.util.concurrent.Future; +import java.util.concurrent.RunnableFuture; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * A specialised alternative to {@link java.util.concurrent.ExecutorCompletionService}, + * used for submitting chunk tasks and queuing their results. + * + * Whilst this class adheres to the {@link CompletionService} interface, use of the class's + * {@link #submit(Callable, Vector3ic)} overload is preferred over those inherited from the interface. + */ +public class ChunkExecutorCompletionService implements CompletionService { + private static final Vector3ic EMPTY_VECTOR3I = new Vector3i(); + private final ThreadPoolExecutor threadPoolExecutor; + private final BlockingQueue> completionQueue; + + private final class ChunkFutureWithCompletion extends PositionFuture { + ChunkFutureWithCompletion(Callable callable, Vector3ic position) { + super(callable, position); + } + + ChunkFutureWithCompletion(Runnable runnable, Chunk result, Vector3ic position) { + super(runnable, result, position); + } + + @Override + protected void done() { + super.done(); + try { + completionQueue.put(this); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } + + public ChunkExecutorCompletionService(ThreadPoolExecutor threadPoolExecutor, BlockingQueue> completionQueue) { + this.threadPoolExecutor = threadPoolExecutor; + this.completionQueue = completionQueue; + } + + /** + * Submits a task to be executed. + * @param callable the task to submit + * + * @deprecated Use {@link #submit(Callable, Vector3ic)} instead + */ + @Override + @Deprecated + public Future submit(Callable callable) { + RunnableFuture task = new ChunkFutureWithCompletion(callable, EMPTY_VECTOR3I); + threadPoolExecutor.execute(task); + return task; + } + + /** + * Submits a chunk task to be executed. + * @param callable the chunk task to execute. + * @param position the position of the chunk. + * @return the submitted task. + */ + public Future submit(Callable callable, Vector3ic position) { + RunnableFuture task = new ChunkFutureWithCompletion(callable, position); + threadPoolExecutor.execute(task); + return task; + } + + /** + * Submits a task to be executed. + * @param runnable the task to run. + * @param value the value to return upon task completion. + * + * @deprecated Use {@link #submit(Callable, Vector3ic)} instead + */ + @Override + @Deprecated + public Future submit(Runnable runnable, Chunk value) { + RunnableFuture task = new ChunkFutureWithCompletion(runnable, value, EMPTY_VECTOR3I); + threadPoolExecutor.execute(task); + return task; + } + + /** + * Retrieves a completed task from the queue. + * @return a completed task. + * @throws InterruptedException if interrupted whilst waiting on the queue. + */ + @Override + public Future take() throws InterruptedException { + return completionQueue.take(); + } + + /** + * Retrieves a completed task from the queue if not empty. + * @return a completed task, or null if there are no tasks in the queue. + */ + @Override + public Future poll() { + return completionQueue.poll(); + } + + /** + * Retrieves a completed task from the queue if not empty. + * @param l the timeout duration before returning null. + * @param timeUnit the time units of the timeout duration. + * + * @return a completed task, or null if there are no tasks in the queue. + */ + @Override + public Future poll(long l, TimeUnit timeUnit) throws InterruptedException { + return completionQueue.poll(l, timeUnit); + } +} diff --git a/engine/src/main/java/org/terasology/engine/world/chunks/pipeline/ChunkProcessingPipeline.java b/engine/src/main/java/org/terasology/engine/world/chunks/pipeline/ChunkProcessingPipeline.java index 5323ec245c5..15fce52938f 100644 --- a/engine/src/main/java/org/terasology/engine/world/chunks/pipeline/ChunkProcessingPipeline.java +++ b/engine/src/main/java/org/terasology/engine/world/chunks/pipeline/ChunkProcessingPipeline.java @@ -13,7 +13,6 @@ import org.slf4j.LoggerFactory; import org.terasology.engine.monitoring.ThreadActivity; import org.terasology.engine.monitoring.ThreadMonitor; -import org.terasology.engine.utilities.ReflectionUtil; import org.terasology.engine.world.chunks.Chunk; import org.terasology.engine.world.chunks.pipeline.stages.ChunkTask; import org.terasology.engine.world.chunks.pipeline.stages.ChunkTaskProvider; @@ -21,14 +20,10 @@ import java.util.Comparator; import java.util.List; import java.util.Map; -import java.util.concurrent.Callable; import java.util.concurrent.CancellationException; -import java.util.concurrent.CompletionService; import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorCompletionService; import java.util.concurrent.Future; import java.util.concurrent.PriorityBlockingQueue; -import java.util.concurrent.RunnableFuture; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.function.Function; @@ -50,7 +45,7 @@ public class ChunkProcessingPipeline { private final List stages = Lists.newArrayList(); private final Thread reactor; - private final CompletionService chunkProcessor; + private final ChunkExecutorCompletionService chunkProcessor; private final ThreadPoolExecutor executor; private final Function chunkProvider; private final Map chunkProcessingInfoMap = Maps.newConcurrentMap(); @@ -66,17 +61,11 @@ public ChunkProcessingPipeline(Function chunkProvider, Compara NUM_TASK_THREADS, NUM_TASK_THREADS, 0L, TimeUnit.MILLISECONDS, - new PriorityBlockingQueue(800, unwrappingComporator(comparable)), + new PriorityBlockingQueue(800, comparable), this::threadFactory, - this::rejectQueueHandler) { - @Override - protected RunnableFuture newTaskFor(Callable callable) { - RunnableFuture newTaskFor = super.newTaskFor(callable); - return new PositionFuture<>(newTaskFor, ((PositionalCallable) callable).getPosition()); - } - }; + this::rejectQueueHandler); logger.debug("allocated {} threads", NUM_TASK_THREADS); - chunkProcessor = new ExecutorCompletionService<>(executor, + chunkProcessor = new ChunkExecutorCompletionService(executor, new PriorityBlockingQueue<>(800, comparable)); reactor = new Thread(this::chunkTaskHandler); reactor.setDaemon(true); @@ -84,18 +73,6 @@ protected RunnableFuture newTaskFor(Callable callable) { reactor.start(); } - /** - * BlackMagic method: {@link ExecutorCompletionService} wraps task with QueueingFuture (private access) - * there takes wrapped task for comparing in {@link ThreadPoolExecutor} - */ - private Comparator unwrappingComporator(Comparator> comparable) { - return (o1, o2) -> { - Object unwrapped1 = ReflectionUtil.readField(o1, "task"); - Object unwrapped2 = ReflectionUtil.readField(o2, "task"); - return comparable.compare((Future) unwrapped1, (Future) unwrapped2); - }; - } - /** * Reactor thread. Handles all ChunkTask dependency logic and running. */ @@ -190,11 +167,11 @@ private Chunk getChunkBy(ChunkTaskProvider requiredStage, Vector3ic position) { } private Future runTask(ChunkTask task, List chunks) { - return chunkProcessor.submit(new PositionalCallable(() -> { + return chunkProcessor.submit(() -> { try (ThreadActivity ignored = ThreadMonitor.startThreadActivity(task.getName())) { return task.apply(chunks); } - }, task.getPosition())); + }, task.getPosition()); } private Thread threadFactory(Runnable runnable) { @@ -236,8 +213,7 @@ public ListenableFuture invokeGeneratorTask(Vector3ic position, Supplier< SettableFuture exitFuture = SettableFuture.create(); chunkProcessingInfo = new ChunkProcessingInfo(position, exitFuture); chunkProcessingInfoMap.put(position, chunkProcessingInfo); - chunkProcessingInfo.setCurrentFuture(chunkProcessor.submit(new PositionalCallable(generatorTask::get, - position))); + chunkProcessingInfo.setCurrentFuture(chunkProcessor.submit(generatorTask::get, position)); return exitFuture; } } @@ -317,26 +293,4 @@ public boolean isPositionProcessing(Vector3ic pos) { public Iterable getProcessingPosition() { return chunkProcessingInfoMap.keySet(); } - - /** - * Dummy callable for passthru position for {@link java.util.concurrent.ThreadPoolExecutor}#newTaskFor - */ - private static final class PositionalCallable implements Callable { - private final Callable callable; - private final Vector3ic position; - - private PositionalCallable(Callable callable, Vector3ic position) { - this.callable = callable; - this.position = position; - } - - public Vector3ic getPosition() { - return position; - } - - @Override - public Chunk call() throws Exception { - return callable.call(); - } - } } diff --git a/engine/src/main/java/org/terasology/engine/world/chunks/pipeline/PositionFuture.java b/engine/src/main/java/org/terasology/engine/world/chunks/pipeline/PositionFuture.java index 5ce0e3452a4..51791bc741c 100644 --- a/engine/src/main/java/org/terasology/engine/world/chunks/pipeline/PositionFuture.java +++ b/engine/src/main/java/org/terasology/engine/world/chunks/pipeline/PositionFuture.java @@ -5,53 +5,23 @@ import org.joml.Vector3ic; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.RunnableFuture; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; +import java.util.concurrent.Callable; +import java.util.concurrent.FutureTask; -public class PositionFuture implements RunnableFuture { - - private final RunnableFuture delegate; +public class PositionFuture extends FutureTask { private final Vector3ic position; - public PositionFuture(RunnableFuture delegate, Vector3ic position) { - this.delegate = delegate; + public PositionFuture(Callable callable, Vector3ic position) { + super(callable); this.position = position; } - public Vector3ic getPosition() { - return position; - } - - @Override - public void run() { - delegate.run(); - } - - @Override - public boolean cancel(boolean mayInterruptIfRunning) { - return delegate.cancel(mayInterruptIfRunning); - } - - @Override - public boolean isCancelled() { - return delegate.isCancelled(); - } - - @Override - public boolean isDone() { - return delegate.isDone(); - } - - @Override - public T get() throws InterruptedException, ExecutionException { - return delegate.get(); + public PositionFuture(Runnable runnable, T result, Vector3ic position) { + super(runnable, result); + this.position = position; } - @Override - public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, - TimeoutException { - return delegate.get(timeout, unit); + public Vector3ic getPosition() { + return position; } } From e710b362d6e496841deaa92e5d19aaf84baa359a Mon Sep 17 00:00:00 2001 From: jdrueckert Date: Sun, 3 Sep 2023 21:29:18 +0200 Subject: [PATCH 028/100] chore: consume CrashReporter v5.0.0 (#5131) - GDrive support was removed with CrashReporter v5 --- engine/build.gradle | 2 +- facades/PC/build.gradle.kts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/engine/build.gradle b/engine/build.gradle index 2695afd67c6..6c8472db065 100644 --- a/engine/build.gradle +++ b/engine/build.gradle @@ -154,7 +154,7 @@ dependencies { api fileTree(dir: 'libs', include: '*.jar') // TODO: Consider moving this back to the PC Facade instead of having the engine rely on it? - implementation group: 'org.terasology.crashreporter', name: 'cr-terasology', version: '4.2.0' + implementation group: 'org.terasology.crashreporter', name: 'cr-terasology', version: '5.0.0' api(project(":subsystems:TypeHandlerLibrary")) } diff --git a/facades/PC/build.gradle.kts b/facades/PC/build.gradle.kts index 2c95561ad69..2d4e02fac18 100644 --- a/facades/PC/build.gradle.kts +++ b/facades/PC/build.gradle.kts @@ -69,7 +69,7 @@ dependencies { implementation("io.projectreactor:reactor-core:3.4.7") // TODO: Consider whether we can move the CR dependency back here from the engine, where it is referenced from the main menu - implementation(group = "org.terasology.crashreporter", name = "cr-terasology", version = "4.2.0") + implementation(group = "org.terasology.crashreporter", name = "cr-terasology", version = "5.0.0") runtimeOnly("ch.qos.logback:logback-classic:1.2.11") { because("to configure logging with logback.xml") From 43a7e053cca8e7c31a7bac6aa833316282586c36 Mon Sep 17 00:00:00 2001 From: Nail Khanipov Date: Mon, 4 Sep 2023 00:02:19 +0300 Subject: [PATCH 029/100] build(gradle): upgrade gradle 6.4.2 >>> 8.2.1 (#5109) * fix junitXmlReport.isEnabled -> required * fix several declaration of `copyResourcesToClasses`. unitTests and integrationTests works now * fix undeclared implicit dependency gradle errors * update groovyw wrapper to work with Gradle 8 * use kotlin-1.9, force java-11 target * update spotbugs version for gradle 7+, java-17, java-20 (#5126) Co-authored-by: Benjamin Amos Co-authored-by: jdrueckert Co-authored-by: Tobias Nett Co-authored-by: soloturn --- build-logic/build.gradle.kts | 8 +- .../main/kotlin/terasology-metrics.gradle.kts | 17 +- .../main/kotlin/terasology-module.gradle.kts | 8 +- build.gradle | 14 +- config/gradle/common.gradle | 5 +- engine-tests/build.gradle | 21 +- engine/build.gradle | 10 +- gradle/wrapper/gradle-wrapper.jar | Bin 59203 -> 63375 bytes gradle/wrapper/gradle-wrapper.properties | 4 +- gradle/wrapper/groovy-wrapper.jar | Bin 5280 -> 5499 bytes gradlew | 281 +++++++++++------- gradlew.bat | 15 +- subsystems/DiscordRPC/build.gradle.kts | 6 + .../TypeHandlerLibrary/build.gradle.kts | 6 + subsystems/build.gradle | 2 +- 15 files changed, 247 insertions(+), 150 deletions(-) diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts index fb432bf51c1..39955b5b511 100644 --- a/build-logic/build.gradle.kts +++ b/build-logic/build.gradle.kts @@ -4,7 +4,11 @@ import java.net.URI plugins { - `kotlin-dsl` + id("org.gradle.kotlin.kotlin-dsl") version "4.1.0" +} + +kotlin { + jvmToolchain(11) } repositories { @@ -46,7 +50,7 @@ dependencies { implementation("org.terasology.gestalt:gestalt-module:7.1.0") // plugins we configure - implementation("com.github.spotbugs.snom:spotbugs-gradle-plugin:4.8.0") // TODO: upgrade with gradle 7.x + implementation("com.github.spotbugs.snom:spotbugs-gradle-plugin:5.1.3") implementation("org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.3") api(kotlin("test")) diff --git a/build-logic/src/main/kotlin/terasology-metrics.gradle.kts b/build-logic/src/main/kotlin/terasology-metrics.gradle.kts index 2c57d92a194..d9c901d9323 100644 --- a/build-logic/src/main/kotlin/terasology-metrics.gradle.kts +++ b/build-logic/src/main/kotlin/terasology-metrics.gradle.kts @@ -50,7 +50,7 @@ tasks.withType { // If false, the outputs are still collected and visible in the test report, but they don't spam the console. testLogging.showStandardStreams = false reports { - junitXml.isEnabled = true + junitXml.required.set(true) } jvmArgs("-Xms512m", "-Xmx1024m") @@ -60,6 +60,18 @@ tasks.withType { } } +tasks.withType { + dependsOn(tasks.getByPath(":extractConfig")) +} + +tasks.withType { + dependsOn(tasks.getByPath(":extractConfig")) +} + +tasks.withType { + dependsOn(tasks.getByPath(":extractConfig")) +} + // The config files here work in both a multi-project workspace (IDEs, running from source) and for solo module builds // Solo module builds in Jenkins get a copy of the config dir from the engine harness so it still lives at root/config // TODO: Maybe update other projects like modules to pull the zipped dependency so fewer quirks are needed in Jenkins @@ -81,9 +93,6 @@ configure { } configure { - // The version of the spotbugs tool https://github.com/spotbugs/spotbugs - // not necessarily the same as the version of spotbugs-gradle-plugin - toolVersion.set("4.7.0") ignoreFailures.set(true) excludeFilter.set(file(rootDir.resolve("config/metrics/findbugs/findbugs-exclude.xml"))) } diff --git a/build-logic/src/main/kotlin/terasology-module.gradle.kts b/build-logic/src/main/kotlin/terasology-module.gradle.kts index 9c48409bea9..dbb7aa58886 100644 --- a/build-logic/src/main/kotlin/terasology-module.gradle.kts +++ b/build-logic/src/main/kotlin/terasology-module.gradle.kts @@ -29,10 +29,10 @@ apply(from = "$rootDir/config/gradle/publish.gradle") // Handle some logic related to where what is configure { main { - java.outputDir = buildDir.resolve("classes") + java.destinationDirectory.set(buildDir.resolve("classes")) } test { - java.outputDir = buildDir.resolve("testClasses") + java.destinationDirectory.set(buildDir.resolve("testClasses")) } } @@ -144,6 +144,10 @@ tasks.named("processResources") { dependsOn("syncAssets", "syncOverrides", "syncDeltas", "syncModuleInfo") } +tasks.named("compileJava") { + dependsOn("processResources") +} + tasks.named("test") { description = "Runs all tests (slow)" useJUnitPlatform () diff --git a/build.gradle b/build.gradle index 6c4da8f63fc..f782d1fe32c 100644 --- a/build.gradle +++ b/build.gradle @@ -56,7 +56,7 @@ import static org.gradle.internal.logging.text.StyledTextOutput.Style // Test for right version of Java in use for running this script assert org.gradle.api.JavaVersion.current().isJava11Compatible() -if(!(JavaVersion.current() == JavaVersion.VERSION_11)) { +if (!(JavaVersion.current() == JavaVersion.VERSION_11)) { def out = services.get(StyledTextOutputFactory).create("an-ouput") out.withStyle(Style.FailureHeader).println(""" WARNING: Compiling with a JDK of not version 11. While some other Javas may be @@ -95,7 +95,7 @@ configurations { dependencies { // For the "natives" configuration make it depend on the native files from LWJGL natives platform("org.lwjgl:lwjgl-bom:$LwjglVersion") - ["natives-linux","natives-windows","natives-macos"].forEach { + ["natives-linux", "natives-windows", "natives-macos"].forEach { natives "org.lwjgl:lwjgl::$it" natives "org.lwjgl:lwjgl-assimp::$it" natives "org.lwjgl:lwjgl-glfw::$it" @@ -151,12 +151,12 @@ task extractJNLuaNatives(type: Copy) { into("$dirNatives") } -task extractNativeBulletNatives(type:Copy) { +task extractNativeBulletNatives(type: Copy) { description = "Extracts the JNBullet natives from the downloaded zip" from { configurations.natives.collect { it.getName().contains('JNBullet') ? zipTree(it) : [] } } - into ("$dirNatives") + into("$dirNatives") } @@ -195,8 +195,8 @@ clean.doLast { allprojects { configurations.all { resolutionStrategy.dependencySubstitution { - substitute module("org.terasology.engine:engine") because "we have sources!" with project(":engine") - substitute module("org.terasology.engine:engine-tests") because "we have sources!" with project(":engine-tests") + substitute module("org.terasology.engine:engine") using project(":engine") because "we have sources!" + substitute module("org.terasology.engine:engine-tests") using project(":engine-tests") because "we have sources!" } } } @@ -207,7 +207,7 @@ project(":modules").subprojects.forEach { proj -> project(":modules").subprojects { configurations.all { resolutionStrategy.dependencySubstitution { - substitute module("org.terasology.modules:${proj.name}") because "we have sources!" with project(":modules:${proj.name}") + substitute module("org.terasology.modules:${proj.name}") using project(":modules:${proj.name}") because "we have sources!" } } } diff --git a/config/gradle/common.gradle b/config/gradle/common.gradle index 0bb76c48bbf..c4b305836ae 100644 --- a/config/gradle/common.gradle +++ b/config/gradle/common.gradle @@ -10,14 +10,11 @@ apply plugin: 'idea' apply plugin: 'terasology-repositories' -java { - sourceCompatibility = JavaVersion.VERSION_11 -} - javadoc.options.encoding = 'UTF-8' tasks.withType(JavaCompile) { options.encoding = 'UTF-8' + options.release.set(11) } task sourceJar(type: Jar) { diff --git a/engine-tests/build.gradle b/engine-tests/build.gradle index cea4bf94360..fab04533599 100644 --- a/engine-tests/build.gradle +++ b/engine-tests/build.gradle @@ -45,8 +45,8 @@ println "Version for $project.name loaded as $version for group $group" sourceSets { // Adjust output path (changed with the Gradle 6 upgrade, this puts it back) - main.java.outputDir = new File("$buildDir/classes") - test.java.outputDir = new File("$buildDir/testClasses") + main.java.destinationDirectory = new File("$buildDir/classes") + test.java.destinationDirectory = new File("$buildDir/testClasses") } // Primary dependencies definition @@ -88,14 +88,21 @@ dependencies { // See terasology-metrics for other test-only internal dependencies } +//TODO: Remove it when gestalt will can to handle ProtectionDomain without classes (Resources) task copyResourcesToClasses(type:Copy) { - from sourceSets.main.output.resourcesDir + from tasks.named("processResources") into sourceSets.main.output.classesDirs.first() } +tasks.named("compileJava"){ + dependsOn(copyResourcesToClasses) +} + +jar { // Workaround about previous copy to classes. idk why engine-tests:jar called before :engine ... + duplicatesStrategy = "EXCLUDE" +} + test { - //TODO: Remove it when gestalt will can to handle ProtectionDomain without classes (Resources) - dependsOn copyResourcesToClasses dependsOn rootProject.extractNatives description("Runs all tests (slow)") @@ -104,8 +111,6 @@ test { } task unitTest(type: Test) { - //TODO: Remove it when gestalt will can to handle ProtectionDomain without classes (Resources) - dependsOn copyResourcesToClasses dependsOn rootProject.extractNatives group "Verification" @@ -118,8 +123,6 @@ task unitTest(type: Test) { } task integrationTest(type: Test) { - //TODO: Remove it when gestalt will can to handle ProtectionDomain without classes (Resources) - dependsOn copyResourcesToClasses dependsOn rootProject.extractNatives group "Verification" diff --git a/engine/build.gradle b/engine/build.gradle index 6c8472db065..e10ec9d3b40 100644 --- a/engine/build.gradle +++ b/engine/build.gradle @@ -45,8 +45,9 @@ sourceSets { } java { // Adjust output path (changed with the Gradle 6 upgrade, this puts it back) - outputDir = new File("$buildDir/classes") + destinationDirectory = new File("$buildDir/classes") } + test.java.destinationDirectory = new File("$buildDir/testClasses") } } @@ -189,6 +190,7 @@ sourceSets { java.srcDirs = ['src/jmh/java'] resources.srcDirs = ['src/jmh/resources'] compileClasspath += sourceSets.main.runtimeClasspath + java.destinationDirectory = new File("$buildDir/jmhClasses") } } @@ -263,14 +265,12 @@ tasks.named("processResources", Copy) { //TODO: Remove this when gestalt can handle ProtectionDomain without classes (Resources) task copyResourcesToClasses(type: Copy) { - from sourceSets.main.output.resourcesDir + from processResources into sourceSets.main.output.classesDirs.first() - dependsOn processResources - mustRunAfter compileJava } -tasks.named("classes") { +tasks.named("compileJava") { dependsOn(tasks.named("copyResourcesToClasses")) } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e708b1c023ec8b20f512888fe07c5bd3ff77bb8f..033e24c4cdf41af1ab109bc7f253b2b887023340 100644 GIT binary patch delta 43784 zcmZs?V{~Rwvn?Fkwrv}oj&0kv`NZkiwrxGJZ6_V8V;h}(ea=1Oz4wgq{n%sG*sJ#5 zf2!7;yQ=2UEO`74IHZy+I0QzrvO8uX9y%ySa?mnvqNLV8{0DVViJKuj_{N|8_y>I+MxT2rn3@Q#>wn~1E zL?7Lobaaai$f~PJ0h}NW%Jz=o9G(v`1G-hf3`!4Hykd#lU+;7v>s6JhQ7>+NykDU( z+Yh)P+pD#Xf_Ezp%e``u9= zPJL0(x6YXP{nVHO6?>|5I(OEX+z0Fdei~>3M`J^9Le#>-%*cHO2dI9HaE61$a74)& zhG`72UvRy2>GhSbe7%JS*{^BAj@7^R_`#yP-hcCvM0hriNtFRwQ8sdle)1Ez%QcHa$>LXwe6Dl*#&-bzELzYX(EA*tDQjPTN z_Iy$>1z$iZhRJU_kP-6fwFoL`ATuxgebp735D2?M!BWyU#RUSYV9ge!Wt(Ryg2c# zE!mNwc?3zLL{3iQ6)y8~c~s=m4Xqg@K3wMbk>I?_-Uz?T$HzrkC9iM}Pe>ON*;6DX zAAhT%>H<2)Tbb?04j7l5tjnh^NK&u3&|8|x9FUM@&C|WbNe>8;>Zst)1WXai?2}(@ zyKj-PO(w)jgcXLvm(rNz(X6t9Y6OW>TfRtL8F0CuNP|)h%mfKHTyb}1*+arFxFC1U z(N0B0N3Jcz26y6Ul(ROd=0s+E8`mO|%&tmuVsXf&%C< zqfuZ+k9Lczb4Li|3N*~kFWFblf~n^$3YVNq6NL&>E`&s_PXoyxkILoa7=DUIu3pa^ zbjt28Iln9>aXD?uYDI=>%f%@nd!*sacDx%hqN>9K2$r2N)@*sA+415I5pr2{acy3O z0sue0u{}YluJrg2MMai%uCnAS|1>N{x) z9W0h;+;^Q;m5Y1Pds=9%6L2UE$|ziEl`q?2-MJyt_O1MjCN!fwl1)PARjh-GPK-Bu ziBk^EHui_QP^405zFUzBY86#gXxXo~tO_j7sEQS_To{@n19bS|pHyoI$71br)Bq_% zKI<_wiEE{M!;;7m75H6Py81{;GMa{JeMKsHeh#zwafm})ycBUB89KUwLH+8iM(-enyqKq)oHH3hDjL8_*Fweo|@|9cT%{f zO-v5t@vOg5-KrPTr2t#GYJ)8@zQ@<478@tOj3h9r#~9`^Q#5+^aL}Wv7*J`&+|Vx@ z)BRL(#kD~Mwr_&pUDQs^fLcl~6?LZC$n&r)&u_?6eu_~zyQ*6+FP*qf$#iHG z2YPE|ZZ@No$<*C$7mSO|AFFFO$1DJum^n0}UN;9_VcV$ZMmrK0={c8_%D=nHmaMXi zLn410cRCiWs7{vSsL)&F+tZG2uyQKop$)mm`wetpa|?aR02T6+0J}-$e5Tw~i$ci59}-vZrgrm4909wfTB||ALOyJi_vsmyzG1%`*ElF%_(8r&emW?qs1{D6NY1hdkN6 z07ItKp8bQ!olQMB3Sen0Jy&kX17H59sC?9IOo92e=7EBncWnkSS&@h??=N|%=F1hG zSGJ+o9GvdE<*GX#`6P4GIp->PzJbw*4h;0=NT7%~D)9MW>0b$q#fS zPQU+~T!DiVv3L&u&UQ{7s>gh_Y{*s{)!Z3MmJJ5VyQyVm4*h!+mLYlbxHc7^s-Z|7u?n}w zt~M(a2Sx*m2>=rstd3iQH+HVUSiz-O6tq{s70C%sK{WQ;xh7kT+&hBZD(hK{7|Y4N z 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�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 Qc)^b}ObCdTt-n6~KSqm+J^%m! delta 39466 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 Ar2qf` diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 25d3265315f..c747538fb38 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-all.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradle/wrapper/groovy-wrapper.jar b/gradle/wrapper/groovy-wrapper.jar index 7fadbd5150cc523b5104c87aabab138320b42805..c17384dc4da4a601bf937dc324f408b332ba03b6 100644 GIT binary patch delta 4847 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 ArvLx| delta 4623 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 diff --git a/gradlew b/gradlew index 4f906e0c811..fcb6fca147c 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,67 +17,98 @@ # ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -87,9 +118,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,88 +129,120 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=`expr $i + 1` + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 107acd32c4e..93e3f59f135 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/subsystems/DiscordRPC/build.gradle.kts b/subsystems/DiscordRPC/build.gradle.kts index 403ce95e6e5..8e35a634457 100644 --- a/subsystems/DiscordRPC/build.gradle.kts +++ b/subsystems/DiscordRPC/build.gradle.kts @@ -9,6 +9,12 @@ plugins { apply(from = "$rootDir/config/gradle/common.gradle") +configure { + // Adjust output path (changed with the Gradle 6 upgrade, this puts it back) + main { java.destinationDirectory.set(File("$buildDir/classes")) } + test { java.destinationDirectory.set(File("$buildDir/testClasses")) } +} + dependencies { implementation(project(":engine")) api("com.jagrosh:DiscordIPC:0.4") diff --git a/subsystems/TypeHandlerLibrary/build.gradle.kts b/subsystems/TypeHandlerLibrary/build.gradle.kts index 04d08725f1d..e8e3035d512 100644 --- a/subsystems/TypeHandlerLibrary/build.gradle.kts +++ b/subsystems/TypeHandlerLibrary/build.gradle.kts @@ -12,6 +12,12 @@ apply(from = "$rootDir/config/gradle/publish.gradle") group = "org.terasology.subsystems" version = project(":engine").version +configure { + // Adjust output path (changed with the Gradle 6 upgrade, this puts it back) + main { java.destinationDirectory.set(File("$buildDir/classes")) } + test { java.destinationDirectory.set(File("$buildDir/testClasses")) } +} + dependencies { implementation("org.slf4j:slf4j-api:1.7.32") implementation("net.sf.trove4j:trove4j:3.0.3") diff --git a/subsystems/build.gradle b/subsystems/build.gradle index 0e6771f98f1..cb50dc0bdc5 100644 --- a/subsystems/build.gradle +++ b/subsystems/build.gradle @@ -9,7 +9,7 @@ subprojects { def sourceSets = project.getConvention().getPlugin(JavaPluginConvention.class).sourceSets - sourceSets.main.java.outputDir = new File("$buildDir/classes") + sourceSets.main.java.destinationDirectory = new File("$buildDir/classes") idea { module { // Change around the output a bit From 1ce636849a47e62fd785e8914ee1db21d0d3aab5 Mon Sep 17 00:00:00 2001 From: Tobias Nett Date: Sun, 10 Sep 2023 21:16:21 +0200 Subject: [PATCH 030/100] build: Add Gradle task to assemble module build harness (#5137) * build: Add Gradle task to assemble module build harness * fix: only depend on 'extractConfig' when the root project is Terasology Co-authored-by: jdrueckert --- .../main/kotlin/terasology-metrics.gradle.kts | 22 ++++++-- build.gradle | 50 +++++++++++++++++++ 2 files changed, 67 insertions(+), 5 deletions(-) diff --git a/build-logic/src/main/kotlin/terasology-metrics.gradle.kts b/build-logic/src/main/kotlin/terasology-metrics.gradle.kts index d9c901d9323..e80c14ad19a 100644 --- a/build-logic/src/main/kotlin/terasology-metrics.gradle.kts +++ b/build-logic/src/main/kotlin/terasology-metrics.gradle.kts @@ -60,16 +60,28 @@ tasks.withType { } } -tasks.withType { - dependsOn(tasks.getByPath(":extractConfig")) -} + tasks.withType { + //FIXME: This is a workaround for module harness builds, where the config + // files are part of the harness but the task is not defined. + if (rootProject.name == "Terasology") { + dependsOn(tasks.getByPath(":extractConfig")) + } + } tasks.withType { - dependsOn(tasks.getByPath(":extractConfig")) + //FIXME: This is a workaround for module harness builds, where the config + // files are part of the harness but the task is not defined. + if (rootProject.name == "Terasology") { + dependsOn(tasks.getByPath(":extractConfig")) + } } tasks.withType { - dependsOn(tasks.getByPath(":extractConfig")) + //FIXME: This is a workaround for module harness builds, where the config + // files are part of the harness but the task is not defined. + if (rootProject.name == "Terasology") { + dependsOn(tasks.getByPath(":extractConfig")) + } } // The config files here work in both a multi-project workspace (IDEs, running from source) and for solo module builds diff --git a/build.gradle b/build.gradle index f782d1fe32c..51cef4f4a21 100644 --- a/build.gradle +++ b/build.gradle @@ -282,3 +282,53 @@ idea { cleanIdea.doLast { new File('Terasology.iws').delete() } + +// A task to assemble various files into a single zip for distribution as 'build-harness.zip' for module builds +task assembleBuildHarness(type: Zip) { + description 'Assembles a zip of files useful for module development' + + dependsOn extractNatives + from('natives'){ + include '**/*' + // TODO: use output of extractNatives? + // TODO: which module needs natives to build? + into 'natives' + } + + dependsOn extractConfig + from('config'){ + //include 'gradle/**/*', 'metrics/**/*' + include '**/*' + // TODO: depend on output of extractConfig? + into 'config' + } + + from('gradle'){ + include '**/*' // include all files in 'gradle' + // TODO: exclude groovy jar? + into 'gradle' + } + + from('build-logic'){ + include 'src/**', '*.kts' + into 'build-logic' + } + + from('templates') { + include 'build.gradle' + } + + from('.') { + include 'gradlew' + } + + // include file 'templates/module.logback-test.xml' as 'src/test/resources/logback-test.xml' + from('templates'){ + include 'module.logback-test.xml' + rename 'module.logback-test.xml', 'logback-test.xml' + into 'src/test/resources' + } + + // set the archive name + archiveFileName.set('build-harness.zip') +} \ No newline at end of file From 5957641954fd8a2f521240f95a7dc06a3c8287fe Mon Sep 17 00:00:00 2001 From: jdrueckert Date: Mon, 25 Sep 2023 06:06:54 +0200 Subject: [PATCH 031/100] build: update groovy wrapper (#5141) --- gradle/wrapper/groovy-wrapper.jar | Bin 5499 -> 5497 bytes groovyw.bat | 28 +--------------------------- 2 files changed, 1 insertion(+), 27 deletions(-) diff --git a/gradle/wrapper/groovy-wrapper.jar b/gradle/wrapper/groovy-wrapper.jar index c17384dc4da4a601bf937dc324f408b332ba03b6..5d9efb6ac387ceec00a51083113d1a8f7fa77e83 100644 GIT binary patch delta 3411 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 XOGZ{7c}A1E!k%u-E`Blk_lN%iQ;8?D delta 3424 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 diff --git a/groovyw.bat b/groovyw.bat index d2623ce34fb..3cd68e56e1a 100644 --- a/groovyw.bat +++ b/groovyw.bat @@ -77,33 +77,7 @@ set _SKIP=2 :win9xME_args_slurp if "x%~1" == "x" goto execute -@REM Escapes all arguments containing either "*" or "?" before passing them to the groovy process -:process_args -@REM If we have no more arguments to process then exit the loop -IF "%1"=="" GOTO end -SET ARG=%~1 -@REM "echo "%ARG%" will simply output the argument as an escaped text string -@REM for piping to other applications. This is needed to ensure that the arguments are not expanded. -@REM findstr is a built-in Windows utility (it has been since Windows 2000) -@REM that finds matches in strings based on regular expressions. -@REM The "&&" operator is followed if the findstr program returns an exit code of 0 -@REM Otherwise, the "||" operator is followed instead. -echo "%ARG%" | findstr /C:"\*" 1>nul && ( - SET CMD_LINE_ARGS=%CMD_LINE_ARGS% "%ARG%" -) || ( - echo "%ARG%" | findstr /C:"\?" 1>nul && ( - SET CMD_LINE_ARGS=%CMD_LINE_ARGS% "%ARG%" - ) || ( - SET CMD_LINE_ARGS=%CMD_LINE_ARGS% %ARG% - ) -) -SHIFT -GOTO process_args - -:end -@REM For some reason, the escaped arguments always contain an extra space at the end, which confuses groovy. -@REM This should remove it. -SET CMD_LINE_ARGS=%CMD_LINE_ARGS:~1% +set CMD_LINE_ARGS=%* :execute @rem Setup the command line From f907533ede16322d2b6916947f27917a311b61b0 Mon Sep 17 00:00:00 2001 From: Rasmus Praestholm Date: Tue, 26 Sep 2023 00:25:59 -0400 Subject: [PATCH 032/100] New Artifactory URL (#5142) * chore: update to new Artifactory (plus skip PMD) --- Jenkinsfile | 5 ++--- build-logic/build.gradle.kts | 10 +++------- .../main/kotlin/terasology-repositories.gradle.kts | 6 ++---- build.gradle | 12 +++++------- config/gradle/publish.gradle | 6 ++---- templates/build.gradle | 6 ++---- templates/facades.gradle | 2 +- templates/gradle.properties | 2 +- 8 files changed, 18 insertions(+), 31 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index a3e573e3f2c..3abf6cc4b22 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -121,7 +121,7 @@ pipeline { stage('Analytics') { steps { - sh './gradlew --console=plain check -x test' + sh './gradlew --console=plain check -x test -x pmdMain -x pmdTest -x pmdJmh' // TODO: Probably more cleanly remove PMD overall if no use? } post { always { @@ -140,8 +140,7 @@ pipeline { recordIssues(skipBlames: true, enabledForFailure: true, tools: [ - spotBugs(pattern: '**/build/reports/spotbugs/*.xml', useRankAsPriority: true), - pmdParser(pattern: '**/build/reports/pmd/*.xml') + spotBugs(pattern: '**/build/reports/spotbugs/*.xml', useRankAsPriority: true) ]) recordIssues(skipBlames: true, enabledForFailure: true, diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts index 39955b5b511..7519d159a05 100644 --- a/build-logic/build.gradle.kts +++ b/build-logic/build.gradle.kts @@ -18,18 +18,14 @@ repositories { maven { name = "Terasology Artifactory" - url = URI("http://artifactory.terasology.org/artifactory/virtual-repo-live") - @Suppress("UnstableApiUsage") - isAllowInsecureProtocol = true // 😱 + url = URI("https://artifactory.terasology.io/artifactory/virtual-repo-live") } - // TODO MYSTERY: As of November 7th 2011 virtual-repo-live could no longer be relied on for latest snapshots - Pro feature? + // TODO MYSTERY: As of November 7th 2021 virtual-repo-live could no longer be relied on for latest snapshots - Pro feature? // We've been using it that way for *years* and nothing likewise changed in the area for years as well. This seems to work .... maven { name = "Terasology snapshot locals" - url = URI("http://artifactory.terasology.org/artifactory/terasology-snapshot-local") - @Suppress("UnstableApiUsage") - isAllowInsecureProtocol = true // 😱 + url = URI("https://artifactory.terasology.io/artifactory/terasology-snapshot-local") } } diff --git a/build-logic/src/main/kotlin/terasology-repositories.gradle.kts b/build-logic/src/main/kotlin/terasology-repositories.gradle.kts index 4c68a6a8863..105a1c79d81 100644 --- a/build-logic/src/main/kotlin/terasology-repositories.gradle.kts +++ b/build-logic/src/main/kotlin/terasology-repositories.gradle.kts @@ -44,8 +44,7 @@ repositories { } else { // Our default is the main virtual repo containing everything except repos for testing Artifactory itself name = "Terasology Artifactory" - url = URI("http://artifactory.terasology.org/artifactory/virtual-repo-live") - isAllowInsecureProtocol = true // 😱 + url = URI("https://artifactory.terasology.io/artifactory/virtual-repo-live") } } @@ -53,7 +52,6 @@ repositories { // We've been using it that way for *years* and nothing likewise changed in the area for years as well. This seems to work .... maven { name = "Terasology snapshot locals" - url = URI("http://artifactory.terasology.org/artifactory/terasology-snapshot-local") - isAllowInsecureProtocol = true // 😱 + url = URI("https://artifactory.terasology.io/artifactory/terasology-snapshot-local") } } diff --git a/build.gradle b/build.gradle index 51cef4f4a21..a32fc232606 100644 --- a/build.gradle +++ b/build.gradle @@ -11,16 +11,14 @@ buildscript { maven { // required to provide runtime dependencies to build-logic. name = "Terasology Artifactory" - url = "http://artifactory.terasology.org/artifactory/virtual-repo-live" - allowInsecureProtocol = true // 😱 + url = "https://artifactory.terasology.io/artifactory/virtual-repo-live" } // TODO MYSTERY: As of November 7th 2011 virtual-repo-live could no longer be relied on for latest snapshots - Pro feature? // We've been using it that way for *years* and nothing likewise changed in the area for years as well. This seems to work .... maven { name = "Terasology snapshot locals" - url = "http://artifactory.terasology.org/artifactory/terasology-snapshot-local" - allowInsecureProtocol = true // 😱 + url = "https://artifactory.terasology.io/artifactory/terasology-snapshot-local" } } @@ -64,7 +62,7 @@ safe to use, any newer than 11 may cause issues. If you encounter oddities try Java 11. See https://github.com/MovingBlocks/Terasology/issues/3976. Current detected Java version is ${JavaVersion.current()} - from vendor ${System.getProperty("java.vendor")} + from vendor ${System.getProperty("java.vendor")} located at ${System.getProperty("java.home")} """) } @@ -315,7 +313,7 @@ task assembleBuildHarness(type: Zip) { } from('templates') { - include 'build.gradle' + include 'build.gradle' } from('.') { @@ -331,4 +329,4 @@ task assembleBuildHarness(type: Zip) { // set the archive name archiveFileName.set('build-harness.zip') -} \ No newline at end of file +} diff --git a/config/gradle/publish.gradle b/config/gradle/publish.gradle index 31aeb166936..786b081afec 100644 --- a/config/gradle/publish.gradle +++ b/config/gradle/publish.gradle @@ -14,8 +14,7 @@ publishing { if (rootProject.hasProperty("publishRepo")) { // This first option is good for local testing, you can set a full explicit target repo in gradle.properties - url = "http://artifactory.terasology.org/artifactory/$publishRepo" - allowInsecureProtocol true // 😱 + url = "https://artifactory.terasology.io/artifactory/$publishRepo" logger.info("Changing PUBLISH repoKey set via Gradle property to {}", publishRepo) } else { // Support override from the environment to use a different target publish org @@ -38,8 +37,7 @@ publishing { } logger.info("The final deduced publish repo is {}", deducedPublishRepo) - url = "http://artifactory.terasology.org/artifactory/$deducedPublishRepo" - allowInsecureProtocol true + url = "https://artifactory.terasology.io/artifactory/$deducedPublishRepo" } } diff --git a/templates/build.gradle b/templates/build.gradle index b05d96aa3fe..5a4a69661de 100644 --- a/templates/build.gradle +++ b/templates/build.gradle @@ -7,16 +7,14 @@ buildscript { maven { // required to provide runtime dependencies to build-logic. name = "Terasology Artifactory" - url = "http://artifactory.terasology.org/artifactory/virtual-repo-live" - allowInsecureProtocol = true // 😱 + url = "https://artifactory.terasology.io/artifactory/virtual-repo-live" } // TODO MYSTERY: As of November 7th 2011 virtual-repo-live could no longer be relied on for latest snapshots - Pro feature? // We've been using it that way for *years* and nothing likewise changed in the area for years as well. This seems to work .... maven { name = "Terasology snapshot locals" - url = "http://artifactory.terasology.org/artifactory/terasology-snapshot-local" - allowInsecureProtocol = true // 😱 + url = "https://artifactory.terasology.io/artifactory/terasology-snapshot-local" } } } diff --git a/templates/facades.gradle b/templates/facades.gradle index b5eb74306f2..7733b3e024e 100644 --- a/templates/facades.gradle +++ b/templates/facades.gradle @@ -12,7 +12,7 @@ repositories { // MovingBlocks Artifactory instance for libs not readily available elsewhere plus our own libs maven { name "Terasology Artifactory" - url "http://artifactory.terasology.org/artifactory/repo" + url "https://artifactory.terasology.io/artifactory/repo" } } diff --git a/templates/gradle.properties b/templates/gradle.properties index 0461586709b..5dd1b59553c 100644 --- a/templates/gradle.properties +++ b/templates/gradle.properties @@ -12,7 +12,7 @@ org.gradle.jvmargs=-Xmx1024m # To pass via CLI use: -Dorg.gradle.internal.publish.checksums.insecure=true # Alternative resolution repo to use - this can be used to ignore "live" artifacts and only accept experimental ones. -# alternativeResolutionRepo=http://artifactory.terasology.org/artifactory/virtual-nanoware-and-remote +# alternativeResolutionRepo=https://artifactory.terasology.io/artifactory/virtual-nanoware-and-remote # Credentials for publishing to Artifactory. Good for local testing # mavenUser= From c7d9a373c5b0ce2a052f28a098af84930b42ded7 Mon Sep 17 00:00:00 2001 From: soloturn Date: Sun, 17 Sep 2023 22:29:16 +0200 Subject: [PATCH 033/100] gradle 8.3, supporting java-20 via its kotlin dependency --- build-logic/build.gradle.kts | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts index 7519d159a05..89af65d3e1c 100644 --- a/build-logic/build.gradle.kts +++ b/build-logic/build.gradle.kts @@ -4,7 +4,7 @@ import java.net.URI plugins { - id("org.gradle.kotlin.kotlin-dsl") version "4.1.0" + `kotlin-dsl` } kotlin { diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index c747538fb38..d11cdd907dd 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME From bef4757518a0077481a4a10179afd83aa9739848 Mon Sep 17 00:00:00 2001 From: soloturn Date: Sun, 8 Oct 2023 00:56:25 +0200 Subject: [PATCH 034/100] gradle-8.4, fix pmd warnings gradle-8.4 fully supports java-21, update link in comment because of new gradle version. codemetrics-2.1.0, has the pmd rules inlined, so warnings are gone now. see MovingBlocks/TeraConfig#19 and MovingBlocks/TeraConfig#22. update pmd to 6.55.0, last release before 7.0.0. update spotbugs gradle plugin to 5.2.0 to makeit work with java-21. --- build-logic/build.gradle.kts | 2 +- build-logic/src/main/kotlin/terasology-metrics.gradle.kts | 6 +++--- build.gradle | 2 +- engine/build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts index 89af65d3e1c..af1854223f7 100644 --- a/build-logic/build.gradle.kts +++ b/build-logic/build.gradle.kts @@ -46,7 +46,7 @@ dependencies { implementation("org.terasology.gestalt:gestalt-module:7.1.0") // plugins we configure - implementation("com.github.spotbugs.snom:spotbugs-gradle-plugin:5.1.3") + implementation("com.github.spotbugs.snom:spotbugs-gradle-plugin:5.2.0") implementation("org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.3") api(kotlin("test")) diff --git a/build-logic/src/main/kotlin/terasology-metrics.gradle.kts b/build-logic/src/main/kotlin/terasology-metrics.gradle.kts index e80c14ad19a..123664364e4 100644 --- a/build-logic/src/main/kotlin/terasology-metrics.gradle.kts +++ b/build-logic/src/main/kotlin/terasology-metrics.gradle.kts @@ -14,8 +14,8 @@ plugins { } dependencies { - "pmd"("net.sourceforge.pmd:pmd-core:6.15.0") - "pmd"("net.sourceforge.pmd:pmd-java:6.15.0") + pmd("net.sourceforge.pmd:pmd-core:6.55.0") + pmd("net.sourceforge.pmd:pmd-java:6.55.0") testRuntimeOnly("ch.qos.logback:logback-classic:1.2.11") { because("runtime: to configure logging during tests with logback.xml") @@ -27,7 +27,7 @@ dependencies { because("redirects java.util.logging (from e.g. junit) through slf4j") } - add("testImplementation", platform("org.junit:junit-bom:5.8.1")) + add("testImplementation", platform("org.junit:junit-bom:5.10.0")) testImplementation("org.junit.jupiter:junit-jupiter-api") testImplementation("org.junit.jupiter:junit-jupiter-params") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine") diff --git a/build.gradle b/build.gradle index a32fc232606..dff4da47618 100644 --- a/build.gradle +++ b/build.gradle @@ -104,7 +104,7 @@ dependencies { // Config for our code analytics lives in a centralized repo: https://github.com/MovingBlocks/TeraConfig - codeMetrics group: 'org.terasology.config', name: 'codemetrics', version: '1.6.3', ext: 'zip' + codeMetrics group: 'org.terasology.config', name: 'codemetrics', version: '2.1.0', ext: 'zip' // Natives for JNLua (Kallisti, KComputers) natives group: 'org.terasology.jnlua', name: 'jnlua_natives', version: '0.1.0-SNAPSHOT', ext: 'zip' diff --git a/engine/build.gradle b/engine/build.gradle index e10ec9d3b40..562ae47c417 100644 --- a/engine/build.gradle +++ b/engine/build.gradle @@ -247,7 +247,7 @@ def createVersionInfoFile = tasks.register("createVersionInfoFile", WritePropert // It is a value we can always get (on Jenkins or otherwise) but we don't want local builds // to invalidate their cache whenever the time changes. // TODO: after upgrading to Gradle 6.8, see if we can have it ignore this property specifically: - // https://docs.gradle.org/current/userguide/more_about_tasks.html#sec:property_file_normalization + // https://docs.gradle.org/current/userguide/incremental_build.html#sec:property_file_normalization property("dateTime", startDateTimeString) } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index d11cdd907dd..8838ba97ba0 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME From 254db60dd4e55190895e620a99d6d90efcfcb14b Mon Sep 17 00:00:00 2001 From: jdrueckert Date: Wed, 18 Oct 2023 23:24:59 +0200 Subject: [PATCH 035/100] fix: make entity dump filename windows compatible (#5145) - windows doesn't allow colons (':') in filenames - so far the filename included the timestamp in format YYYY-MM-DDTHH:MM:SS.MSZ, e.g. 2023-09-28T17:01:31.685172Z-entityDump.json - now colons will be replaced by dashes ('-') --- .../terasology/engine/logic/console/commands/CoreCommands.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/src/main/java/org/terasology/engine/logic/console/commands/CoreCommands.java b/engine/src/main/java/org/terasology/engine/logic/console/commands/CoreCommands.java index eb3c0621bb6..5a13e239daf 100644 --- a/engine/src/main/java/org/terasology/engine/logic/console/commands/CoreCommands.java +++ b/engine/src/main/java/org/terasology/engine/logic/console/commands/CoreCommands.java @@ -479,7 +479,7 @@ public String dumpEntities(@CommandParam(value = "componentNames", required = fa PrefabSerializer prefabSerializer = new PrefabSerializer(engineEntityManager.getComponentLibrary(), engineEntityManager.getTypeSerializerLibrary()); WorldDumper worldDumper = new WorldDumper(engineEntityManager, prefabSerializer); - Path outFile = PathManager.getInstance().getHomePath().resolve(Instant.now() + "-entityDump.json"); + Path outFile = PathManager.getInstance().getHomePath().resolve(Instant.now().toString().replaceAll(":", "-") + "-entityDump.json"); if (componentNames.length == 0) { savedEntityCount = worldDumper.save(outFile); } else { From 4d79e5e613659d3ccc5e079c178fd1299fa742c0 Mon Sep 17 00:00:00 2001 From: soloturn Date: Sat, 21 Oct 2023 23:09:04 +0200 Subject: [PATCH 036/100] convert settings.gradle to kotlin (#5147) Co-authored-by: jdrueckert --- settings.gradle | 16 ---------------- settings.gradle.kts | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+), 16 deletions(-) delete mode 100644 settings.gradle create mode 100644 settings.gradle.kts diff --git a/settings.gradle b/settings.gradle deleted file mode 100644 index 2fb04fd5bd6..00000000000 --- a/settings.gradle +++ /dev/null @@ -1,16 +0,0 @@ -import groovy.io.FileType - -rootProject.name = 'Terasology' - -includeBuild("build-logic") -include 'engine', 'engine-tests', 'facades', 'metas', 'libs', 'modules' - -// Handy little snippet found online that'll "fake" having nested settings.gradle files under /modules, /libs, etc -rootDir.eachDir { possibleSubprojectDir -> - - // First scan through all subdirs that has a subprojects.gradle in it and apply that script (recursive search!) - possibleSubprojectDir.eachFileMatch FileType.FILES, ~/subprojects\.settings\.gradle/, { subprojectsSpecificationScript -> - //println "Magic is happening, applying from " + subprojectsSpecificationScript - apply from: subprojectsSpecificationScript - } -} diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 00000000000..d50b1d33739 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,18 @@ +rootProject.name = "Terasology" + +includeBuild("build-logic") +include("engine", "engine-tests", "facades", "metas", "libs", "modules") + +// Handy little snippet found online that'll "fake" having nested settings.gradle files under /modules, /libs, etc +rootDir.listFiles()?.forEach { possibleSubprojectDir -> + if (possibleSubprojectDir.isDirectory && possibleSubprojectDir.name != ".gradle") { + possibleSubprojectDir.walkTopDown().forEach { it.listFiles { file -> + file.isFile && file.name == "subprojects.settings.gradle" }?.forEach { subprojectsSpecificationScript -> + //println("Magic is happening, applying from $subprojectsSpecificationScript") + apply { + from(subprojectsSpecificationScript) + } + } + } + } +} From c3e246699944daf8d608dd68a094f2ed8cd5713a Mon Sep 17 00:00:00 2001 From: Robert O'Shea Date: Mon, 21 Aug 2023 03:05:26 +0100 Subject: [PATCH 037/100] Update build files to remove deprecation warnings --- .../main/kotlin/org/terasology/gradology/module_deps.kt | 5 +---- .../src/main/kotlin/reflections-manifest.gradle.kts | 2 +- build-logic/src/main/kotlin/terasology-module.gradle.kts | 8 ++++---- build.gradle | 2 +- engine/build.gradle | 2 +- facades/PC/build.gradle.kts | 6 +++--- subsystems/DiscordRPC/build.gradle.kts | 4 ++-- subsystems/TypeHandlerLibrary/build.gradle.kts | 4 ++-- subsystems/build.gradle | 3 +-- 9 files changed, 16 insertions(+), 20 deletions(-) diff --git a/build-logic/src/main/kotlin/org/terasology/gradology/module_deps.kt b/build-logic/src/main/kotlin/org/terasology/gradology/module_deps.kt index 3807ee13a29..84c95892f24 100644 --- a/build-logic/src/main/kotlin/org/terasology/gradology/module_deps.kt +++ b/build-logic/src/main/kotlin/org/terasology/gradology/module_deps.kt @@ -90,13 +90,10 @@ fun moduleDependencyOrdering(modulesConfig: Configuration): List { // configurations.resolvedConfiguration is more straightforward if you just want all the artifacts, // but using `.incoming` lets us turn on lenient mode as well as do more accurate filtering of local modules val resolvable = modulesConfig.incoming - val artifactView = resolvable.artifactView { - lenient(true) - } val result = resolvable.resolutionResult val allDependencies = result.allDependencies - val resolvedDependencies = allDependencies.mapNotNull { + allDependencies.mapNotNull { if (it is ResolvedDependencyResult) { return@mapNotNull it } diff --git a/build-logic/src/main/kotlin/reflections-manifest.gradle.kts b/build-logic/src/main/kotlin/reflections-manifest.gradle.kts index 4696090dbb0..f9650d36b25 100644 --- a/build-logic/src/main/kotlin/reflections-manifest.gradle.kts +++ b/build-logic/src/main/kotlin/reflections-manifest.gradle.kts @@ -11,7 +11,7 @@ import java.net.URLClassLoader tasks.register("cacheReflections") { description = "Caches reflection output to make regular startup faster. May go stale and need cleanup at times." - val sourceSets = project.convention.getPlugin(JavaPluginConvention::class.java).sourceSets + val sourceSets = project.extensions.getByType(SourceSetContainer::class.java) val mainSourceSet: SourceSet = sourceSets[SourceSet.MAIN_SOURCE_SET_NAME] inputs.files(mainSourceSet.output.classesDirs) diff --git a/build-logic/src/main/kotlin/terasology-module.gradle.kts b/build-logic/src/main/kotlin/terasology-module.gradle.kts index dbb7aa58886..cd0c27f5494 100644 --- a/build-logic/src/main/kotlin/terasology-module.gradle.kts +++ b/build-logic/src/main/kotlin/terasology-module.gradle.kts @@ -29,10 +29,10 @@ apply(from = "$rootDir/config/gradle/publish.gradle") // Handle some logic related to where what is configure { main { - java.destinationDirectory.set(buildDir.resolve("classes")) + java.destinationDirectory.set(layout.buildDirectory.dir("classes")) } test { - java.destinationDirectory.set(buildDir.resolve("testClasses")) + java.destinationDirectory.set(layout.buildDirectory.dir("testClasses")) } } @@ -178,8 +178,8 @@ configure { module { // Change around the output a bit inheritOutputDirs = false - outputDir = buildDir.resolve("classes") - testOutputDir = buildDir.resolve("testClasses") + outputDir = layout.buildDirectory.dir("classes").get().asFile + testOutputDir = layout.buildDirectory.dir("testClasses").get().asFile isDownloadSources = true } } diff --git a/build.gradle b/build.gradle index dff4da47618..2c09415de53 100644 --- a/build.gradle +++ b/build.gradle @@ -41,7 +41,7 @@ plugins { // For the "Build and run using: Intellij IDEA | Gradle" switch id "org.jetbrains.gradle.plugin.idea-ext" version "1.0" - id("com.google.protobuf") version "0.8.16" apply false + id("com.google.protobuf") version "0.9.4" apply false id("terasology-repositories") } diff --git a/engine/build.gradle b/engine/build.gradle index 562ae47c417..318dff485e9 100644 --- a/engine/build.gradle +++ b/engine/build.gradle @@ -195,7 +195,7 @@ sourceSets { } task jmh(type: JavaExec, dependsOn: jmhClasses) { - main = 'org.openjdk.jmh.Main' + mainClass = 'org.openjdk.jmh.Main' classpath = sourceSets.jmh.compileClasspath + sourceSets.jmh.runtimeClasspath } diff --git a/facades/PC/build.gradle.kts b/facades/PC/build.gradle.kts index 2d4e02fac18..d1ab573ed71 100644 --- a/facades/PC/build.gradle.kts +++ b/facades/PC/build.gradle.kts @@ -168,7 +168,7 @@ val createVersionFile = tasks.register("createVersionFile") { inputs.property("dateTime", startDateTimeString) from(templatesDir) - into("$buildDir/versionfile") + into(layout.buildDirectory.dir("versionfile").get().asFile) include(versionFileName) expand(mapOf( "buildNumber" to env["BUILD_NUMBER"], @@ -205,7 +205,7 @@ val distForLauncher = tasks.register("distForLauncher") { if (this.sourcePath == "Terasology" || this.sourcePath == "Terasology.bat") { // I don't know how the "lib/" makes its way in to the classpath used by CreateStartScripts, // so we're adjusting it after-the-fact. - filter(ScriptClasspathRewriter(this, defaultLibraryDirectory, launcherLibraryDirectory)) + filter(ScriptClasspathRewriter(this, defaultLibraryDirectory, launcherLibraryDirectory) as Transformer) } } }) @@ -248,7 +248,7 @@ tasks.register("testDist") { dependsOn("testDistForLauncher", "testDistZip") } -class ScriptClasspathRewriter(file: FileCopyDetails, val oldDirectory: String, val newDirectory: String) : Transformer { +class ScriptClasspathRewriter(file: FileCopyDetails, val oldDirectory: String, val newDirectory: String) : Transformer { private val isBatchFile = file.name.endsWith(".bat") override fun transform(line: String): String = if (isBatchFile) { diff --git a/subsystems/DiscordRPC/build.gradle.kts b/subsystems/DiscordRPC/build.gradle.kts index 8e35a634457..2d870a3efb8 100644 --- a/subsystems/DiscordRPC/build.gradle.kts +++ b/subsystems/DiscordRPC/build.gradle.kts @@ -11,8 +11,8 @@ apply(from = "$rootDir/config/gradle/common.gradle") configure { // Adjust output path (changed with the Gradle 6 upgrade, this puts it back) - main { java.destinationDirectory.set(File("$buildDir/classes")) } - test { java.destinationDirectory.set(File("$buildDir/testClasses")) } + main { java.destinationDirectory.set(layout.buildDirectory.dir("classes")) } + test { java.destinationDirectory.set(layout.buildDirectory.dir("testClasses")) } } dependencies { diff --git a/subsystems/TypeHandlerLibrary/build.gradle.kts b/subsystems/TypeHandlerLibrary/build.gradle.kts index e8e3035d512..a205c2c15f2 100644 --- a/subsystems/TypeHandlerLibrary/build.gradle.kts +++ b/subsystems/TypeHandlerLibrary/build.gradle.kts @@ -14,8 +14,8 @@ version = project(":engine").version configure { // Adjust output path (changed with the Gradle 6 upgrade, this puts it back) - main { java.destinationDirectory.set(File("$buildDir/classes")) } - test { java.destinationDirectory.set(File("$buildDir/testClasses")) } + main { java.destinationDirectory.set(layout.buildDirectory.dir("classes")) } + test { java.destinationDirectory.set(layout.buildDirectory.dir("testClasses")) } } dependencies { diff --git a/subsystems/build.gradle b/subsystems/build.gradle index cb50dc0bdc5..87ce4e78edb 100644 --- a/subsystems/build.gradle +++ b/subsystems/build.gradle @@ -6,8 +6,7 @@ subprojects { plugins.apply('java') plugins.apply('idea') - def sourceSets = project.getConvention().getPlugin(JavaPluginConvention.class).sourceSets - + def sourceSets = project.getExtensions().getByType(SourceSetContainer.class) sourceSets.main.java.destinationDirectory = new File("$buildDir/classes") idea { From 5938af4453cf847db7a8e0a86aecb95fcd98c0d4 Mon Sep 17 00:00:00 2001 From: Robert O'Shea Date: Mon, 21 Aug 2023 04:26:29 +0100 Subject: [PATCH 038/100] Fixed a build deprecation warning --- engine/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/build.gradle b/engine/build.gradle index 318dff485e9..48f7303f833 100644 --- a/engine/build.gradle +++ b/engine/build.gradle @@ -251,7 +251,7 @@ def createVersionInfoFile = tasks.register("createVersionInfoFile", WritePropert property("dateTime", startDateTimeString) } - outputFile = "$buildDir/createVersionInfoFile/versionInfo.properties" + destinationFile = layout.buildDirectory.dir("createrVersionInfoFile").get().file("versionInfo.properties") } tasks.named("processResources", Copy) { From ec81b2b3081c47ccecd9b51a881e72573ca6467f Mon Sep 17 00:00:00 2001 From: soloturn Date: Thu, 26 Oct 2023 08:25:25 +0200 Subject: [PATCH 039/100] gradle warnings, integrate review comments from BenjaminAmos cherry picked PurityLakes pr https://github.com/MovingBlocks/Terasology/pull/5140 onto newest develop. kotlin plugin and spotbugs conflict comments are resolved by this. protobuf upgrade should be compabitle, at least according to documentation, so left it there. the lenient in module_deps.kt is again there. --- .../src/main/kotlin/org/terasology/gradology/module_deps.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build-logic/src/main/kotlin/org/terasology/gradology/module_deps.kt b/build-logic/src/main/kotlin/org/terasology/gradology/module_deps.kt index 84c95892f24..810fdda03e5 100644 --- a/build-logic/src/main/kotlin/org/terasology/gradology/module_deps.kt +++ b/build-logic/src/main/kotlin/org/terasology/gradology/module_deps.kt @@ -90,6 +90,9 @@ fun moduleDependencyOrdering(modulesConfig: Configuration): List { // configurations.resolvedConfiguration is more straightforward if you just want all the artifacts, // but using `.incoming` lets us turn on lenient mode as well as do more accurate filtering of local modules val resolvable = modulesConfig.incoming + val artifactView = resolvable.artifactView { + lenient(true) + } val result = resolvable.resolutionResult val allDependencies = result.allDependencies From 34336968f64b575b63d96e38e7e84c6c594b341f Mon Sep 17 00:00:00 2001 From: Rostyslav Zatserkovnyi Date: Tue, 31 Oct 2023 14:21:12 +0200 Subject: [PATCH 040/100] Fix crash when system locale is complex --- .../engine/config/SystemConfig.java | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/engine/src/main/java/org/terasology/engine/config/SystemConfig.java b/engine/src/main/java/org/terasology/engine/config/SystemConfig.java index 9f0aafd1ab6..2e0bf4747aa 100644 --- a/engine/src/main/java/org/terasology/engine/config/SystemConfig.java +++ b/engine/src/main/java/org/terasology/engine/config/SystemConfig.java @@ -11,6 +11,8 @@ import java.util.Locale; import java.util.Locale.Category; import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import static java.lang.Math.max; import static org.terasology.engine.config.flexible.SettingArgument.constraint; @@ -81,7 +83,7 @@ public class SystemConfig extends AutoConfig { public final Setting locale = setting( type(Locale.class), - defaultValue(Locale.getDefault(Category.DISPLAY)), + defaultValue(getAdjustedLocale()), name("${engine:menu#settings-language}"), constraint(new LocaleConstraint(Locale.getAvailableLocales())) // TODO provide translate project's locales (Pirate lang don't works) ); @@ -90,4 +92,20 @@ public class SystemConfig extends AutoConfig { public String getName() { return "${engine:menu#system-settings-title}"; } + + private static Locale getAdjustedLocale() { + Locale systemLocale = Locale.getDefault(Category.DISPLAY); + + // Matches strings like xx_XX. e.g. en_US + final Pattern langRegionPattern = Pattern.compile("[a-z]{2}_[A-Z]{2}"); + + String input = systemLocale.toString(); + Matcher matcher = langRegionPattern.matcher(input); + + // If the locale is like that, convert it to just the language, e.g. en_US -> en + if (matcher.find()) { + systemLocale = Locale.forLanguageTag(systemLocale.getLanguage()); + } + return systemLocale; + } } From 2d8ef61e806efea32f287e6ad6a38c2f9633665d Mon Sep 17 00:00:00 2001 From: Tobias Nett Date: Tue, 31 Oct 2023 14:46:35 +0100 Subject: [PATCH 041/100] doc: move wiki content to docsify page (#5155) * rename docs to docs-pre-merge * add wiki content based on commit e4d4b10424f24eed6583ea0e998da8aa32a27a3f * replace wikilinks with markdown links in _sidebar * use sidebar link text as title via ` autoHeader: true` * rename files with `:` or `,` in the name * use the wiki Home page as entry point instead of the repo README --- {docs => docs-pre-merge}/CODE_OF_CONDUCT.md | 0 {docs => docs-pre-merge}/Credits.md | 0 {docs => docs-pre-merge}/Modules.md | 0 {docs => docs-pre-merge}/Playing.md | 0 .../images/PopulatedVillage.jpg | Bin {docs => docs-pre-merge}/images/discord.png | Bin {docs => docs-pre-merge}/images/facebook.png | Bin {docs => docs-pre-merge}/images/forum.png | Bin .../images/menuBackground.jpg | Bin {docs => docs-pre-merge}/images/patreon.jpg | Bin {docs => docs-pre-merge}/images/reddit.png | Bin .../images/terasology-logo.png | Bin {docs => docs-pre-merge}/images/twitter.png | Bin {docs => docs-pre-merge}/images/youtube.png | Bin docs/.gitignore | 2 + docs/.nojekyll | 0 docs/5.3-Migrating-to-Project-Reactor.md | 40 +++ docs/Advanced-Options.md | 36 +++ docs/Block-Definitions.md | 183 +++++++++++ docs/Block-Shapes.md | 149 +++++++++ docs/Build-Setup.md | 20 ++ docs/Character-Module.md | 102 ++++++ docs/Code-Conventions.md | 293 ++++++++++++++++++ docs/Codebase-Structure.md | 144 +++++++++ docs/Component-Types.md | 58 ++++ docs/Contributor-Quick-Start.md | 82 +++++ docs/Dealing-with-Forks.md | 67 ++++ docs/Developing-Commands.md | 94 ++++++ docs/Developing-Modules.md | 122 ++++++++ docs/DevelopingModules.png | Bin 0 -> 17711 bytes docs/Discord-Integrations.md | 30 ++ docs/Documentation-Guide.md | 68 ++++ docs/Eclipse.md | 27 ++ docs/EclipseFormatterSettings.png | Bin 0 -> 33518 bytes docs/EclipseImports1.png | Bin 0 -> 36749 bytes docs/EclipseImports2.png | Bin 0 -> 35448 bytes ...es-Components-and-Events-on-the-Network.md | 111 +++++++ docs/Entity-System-Architecture.md | 247 +++++++++++++++ docs/Event-Types.md | 209 +++++++++++++ docs/Events-and-Systems.md | 84 +++++ docs/GCI.md | 161 ++++++++++ docs/Home.md | 58 ++++ docs/How-to-Use-Record-and-Replay.md | 19 ++ .../How-to-Work-on-a-PR-Efficiently.mediawiki | 66 ++++ docs/IO-API-for-Modules.md | 49 +++ docs/Interactive-Blocks.md | 141 +++++++++ docs/LightEnvironment/Branch.png | Bin 0 -> 80459 bytes docs/LightEnvironment/Branch1.png | Bin 0 -> 269053 bytes docs/LightEnvironment/Branch2.png | Bin 0 -> 187101 bytes docs/LightEnvironment/Clone.png | Bin 0 -> 76119 bytes docs/LightEnvironment/Commit.png | Bin 0 -> 72873 bytes docs/LightEnvironment/Create.png | Bin 0 -> 268987 bytes docs/LightEnvironment/Delete.png | Bin 0 -> 469117 bytes docs/LightEnvironment/Edit1.png | Bin 0 -> 469284 bytes docs/LightEnvironment/Edit2.png | Bin 0 -> 277579 bytes docs/LightEnvironment/Fork.png | Bin 0 -> 276854 bytes docs/LightEnvironment/PR.png | Bin 0 -> 268716 bytes docs/LightEnvironment/PR_desktop.png | Bin 0 -> 81714 bytes docs/Maintenance.md | 81 +++++ docs/Markdown-and-Wiki.md | 39 +++ docs/Modding-API.md | 28 ++ docs/Module-Dependencies.md | 83 +++++ docs/Module-Security.md | 81 +++++ docs/Module.txt.md | 38 +++ docs/Multi-Repo-Workspace.md | 31 ++ docs/Outreach.md | 30 ++ docs/Play-Test-Setup.md | 104 +++++++ docs/Project-Overview.md | 58 ++++ docs/Randomness-and-Noise.md | 24 ++ docs/Record-and-Replay-Code-Details.md | 27 ++ docs/Release-Modules.md | 109 +++++++ docs/Release-Omega.md | 109 +++++++ docs/Renderer.md | 24 ++ docs/Rendering.md | 30 ++ docs/Replay-Tests.md | 129 ++++++++ docs/Serialization-Overview.md | 53 ++++ docs/Setting-up-a-Light-Environment.md | 89 ++++++ docs/Setup-a-headless-server.md | 15 + docs/Shape-File-Specifications.md | 92 ++++++ docs/Supported-Development-Workflows.md | 96 ++++++ docs/Teracon-2017-Slides-about-multiplayer.md | 78 +++++ docs/Testing-Modules.md | 37 +++ docs/Text-and-Font.md | 61 ++++ docs/Textures.md | 41 +++ docs/Translation-Guide.md | 21 ++ docs/Troubleshooting-Developer.md | 133 ++++++++ docs/Troubleshooting-Player.md | 18 ++ docs/Troubleshooting.md | 20 ++ docs/Using-Locally-Developed-Libraries.md | 53 ++++ docs/What-is-Terasology.md | 48 +++ ...ed-as-an-org-attending-Ludicious.mediawiki | 40 +++ docs/Why-Terasology.md | 105 +++++++ docs/_Footer.md | 3 + docs/_sidebar.md | 68 ++++ docs/activate_addon.png | Bin 0 -> 53094 bytes docs/architecture.png | Bin 0 -> 33911 bytes docs/block-shapes-TerasologyProperties.png | Bin 0 -> 6335 bytes docs/block-shapes-banner.png | Bin 0 -> 975396 bytes docs/block-shapes-exploded.png | Bin 0 -> 19960 bytes docs/forks.png | Bin 0 -> 16797 bytes docs/icons/baseline-bug_report-24px.svg | 1 + docs/icons/baseline-videogame_asset-24px.svg | 1 + docs/images/Build-Setup.png | Bin 0 -> 187996 bytes docs/index.html | 59 ++++ docs/install_addon_blender.png | Bin 0 -> 87517 bytes 105 files changed, 4819 insertions(+) rename {docs => docs-pre-merge}/CODE_OF_CONDUCT.md (100%) rename {docs => docs-pre-merge}/Credits.md (100%) rename {docs => docs-pre-merge}/Modules.md (100%) rename {docs => docs-pre-merge}/Playing.md (100%) rename {docs => docs-pre-merge}/images/PopulatedVillage.jpg (100%) rename {docs => docs-pre-merge}/images/discord.png (100%) rename {docs => docs-pre-merge}/images/facebook.png (100%) rename {docs => docs-pre-merge}/images/forum.png (100%) rename {docs => docs-pre-merge}/images/menuBackground.jpg (100%) rename {docs => docs-pre-merge}/images/patreon.jpg (100%) rename {docs => docs-pre-merge}/images/reddit.png (100%) rename {docs => docs-pre-merge}/images/terasology-logo.png (100%) rename {docs => docs-pre-merge}/images/twitter.png (100%) rename {docs => docs-pre-merge}/images/youtube.png (100%) create mode 100644 docs/.gitignore create mode 100644 docs/.nojekyll create mode 100644 docs/5.3-Migrating-to-Project-Reactor.md create mode 100644 docs/Advanced-Options.md create mode 100644 docs/Block-Definitions.md create mode 100644 docs/Block-Shapes.md create mode 100644 docs/Build-Setup.md create mode 100644 docs/Character-Module.md create mode 100644 docs/Code-Conventions.md create mode 100644 docs/Codebase-Structure.md create mode 100644 docs/Component-Types.md create mode 100644 docs/Contributor-Quick-Start.md create mode 100644 docs/Dealing-with-Forks.md create mode 100644 docs/Developing-Commands.md create mode 100644 docs/Developing-Modules.md create mode 100644 docs/DevelopingModules.png create mode 100644 docs/Discord-Integrations.md create mode 100644 docs/Documentation-Guide.md create mode 100644 docs/Eclipse.md create mode 100644 docs/EclipseFormatterSettings.png create mode 100644 docs/EclipseImports1.png create mode 100644 docs/EclipseImports2.png create mode 100644 docs/Entities-Components-and-Events-on-the-Network.md create mode 100644 docs/Entity-System-Architecture.md create mode 100644 docs/Event-Types.md create mode 100644 docs/Events-and-Systems.md create mode 100644 docs/GCI.md create mode 100644 docs/Home.md create mode 100644 docs/How-to-Use-Record-and-Replay.md create mode 100644 docs/How-to-Work-on-a-PR-Efficiently.mediawiki create mode 100644 docs/IO-API-for-Modules.md create mode 100644 docs/Interactive-Blocks.md create mode 100644 docs/LightEnvironment/Branch.png create mode 100644 docs/LightEnvironment/Branch1.png create mode 100644 docs/LightEnvironment/Branch2.png create mode 100644 docs/LightEnvironment/Clone.png create mode 100644 docs/LightEnvironment/Commit.png create mode 100644 docs/LightEnvironment/Create.png create mode 100644 docs/LightEnvironment/Delete.png create mode 100644 docs/LightEnvironment/Edit1.png create mode 100644 docs/LightEnvironment/Edit2.png create mode 100644 docs/LightEnvironment/Fork.png create mode 100644 docs/LightEnvironment/PR.png create mode 100644 docs/LightEnvironment/PR_desktop.png create mode 100644 docs/Maintenance.md create mode 100644 docs/Markdown-and-Wiki.md create mode 100644 docs/Modding-API.md create mode 100644 docs/Module-Dependencies.md create mode 100644 docs/Module-Security.md create mode 100644 docs/Module.txt.md create mode 100644 docs/Multi-Repo-Workspace.md create mode 100644 docs/Outreach.md create mode 100644 docs/Play-Test-Setup.md create mode 100644 docs/Project-Overview.md create mode 100644 docs/Randomness-and-Noise.md create mode 100644 docs/Record-and-Replay-Code-Details.md create mode 100644 docs/Release-Modules.md create mode 100644 docs/Release-Omega.md create mode 100644 docs/Renderer.md create mode 100644 docs/Rendering.md create mode 100644 docs/Replay-Tests.md create mode 100644 docs/Serialization-Overview.md create mode 100644 docs/Setting-up-a-Light-Environment.md create mode 100644 docs/Setup-a-headless-server.md create mode 100644 docs/Shape-File-Specifications.md create mode 100644 docs/Supported-Development-Workflows.md create mode 100644 docs/Teracon-2017-Slides-about-multiplayer.md create mode 100644 docs/Testing-Modules.md create mode 100644 docs/Text-and-Font.md create mode 100644 docs/Textures.md create mode 100644 docs/Translation-Guide.md create mode 100644 docs/Troubleshooting-Developer.md create mode 100644 docs/Troubleshooting-Player.md create mode 100644 docs/Troubleshooting.md create mode 100644 docs/Using-Locally-Developed-Libraries.md create mode 100644 docs/What-is-Terasology.md create mode 100644 docs/What-we-learned-as-an-org-attending-Ludicious.mediawiki create mode 100644 docs/Why-Terasology.md create mode 100644 docs/_Footer.md create mode 100644 docs/_sidebar.md create mode 100644 docs/activate_addon.png create mode 100644 docs/architecture.png create mode 100644 docs/block-shapes-TerasologyProperties.png create mode 100644 docs/block-shapes-banner.png create mode 100644 docs/block-shapes-exploded.png create mode 100644 docs/forks.png create mode 100644 docs/icons/baseline-bug_report-24px.svg create mode 100644 docs/icons/baseline-videogame_asset-24px.svg create mode 100644 docs/images/Build-Setup.png create mode 100644 docs/index.html create mode 100644 docs/install_addon_blender.png diff --git a/docs/CODE_OF_CONDUCT.md b/docs-pre-merge/CODE_OF_CONDUCT.md similarity index 100% rename from docs/CODE_OF_CONDUCT.md rename to docs-pre-merge/CODE_OF_CONDUCT.md diff --git a/docs/Credits.md b/docs-pre-merge/Credits.md similarity index 100% rename from docs/Credits.md rename to docs-pre-merge/Credits.md diff --git a/docs/Modules.md b/docs-pre-merge/Modules.md similarity index 100% rename from docs/Modules.md rename to docs-pre-merge/Modules.md diff --git a/docs/Playing.md b/docs-pre-merge/Playing.md similarity index 100% rename from docs/Playing.md rename to docs-pre-merge/Playing.md diff --git a/docs/images/PopulatedVillage.jpg b/docs-pre-merge/images/PopulatedVillage.jpg similarity index 100% rename from docs/images/PopulatedVillage.jpg rename to docs-pre-merge/images/PopulatedVillage.jpg diff --git a/docs/images/discord.png b/docs-pre-merge/images/discord.png similarity index 100% rename from docs/images/discord.png rename to docs-pre-merge/images/discord.png diff --git a/docs/images/facebook.png b/docs-pre-merge/images/facebook.png similarity index 100% rename from docs/images/facebook.png rename to docs-pre-merge/images/facebook.png diff --git a/docs/images/forum.png b/docs-pre-merge/images/forum.png similarity index 100% rename from docs/images/forum.png rename to docs-pre-merge/images/forum.png diff --git a/docs/images/menuBackground.jpg b/docs-pre-merge/images/menuBackground.jpg similarity index 100% rename from docs/images/menuBackground.jpg rename to docs-pre-merge/images/menuBackground.jpg diff --git a/docs/images/patreon.jpg b/docs-pre-merge/images/patreon.jpg similarity index 100% rename from docs/images/patreon.jpg rename to docs-pre-merge/images/patreon.jpg diff --git a/docs/images/reddit.png b/docs-pre-merge/images/reddit.png similarity index 100% rename from docs/images/reddit.png rename to docs-pre-merge/images/reddit.png diff --git a/docs/images/terasology-logo.png b/docs-pre-merge/images/terasology-logo.png similarity index 100% rename from docs/images/terasology-logo.png rename to docs-pre-merge/images/terasology-logo.png diff --git a/docs/images/twitter.png b/docs-pre-merge/images/twitter.png similarity index 100% rename from docs/images/twitter.png rename to docs-pre-merge/images/twitter.png diff --git a/docs/images/youtube.png b/docs-pre-merge/images/youtube.png similarity index 100% rename from docs/images/youtube.png rename to docs-pre-merge/images/youtube.png diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 00000000000..29b636a4866 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,2 @@ +.idea +*.iml \ No newline at end of file diff --git a/docs/.nojekyll b/docs/.nojekyll new file mode 100644 index 00000000000..e69de29bb2d diff --git a/docs/5.3-Migrating-to-Project-Reactor.md b/docs/5.3-Migrating-to-Project-Reactor.md new file mode 100644 index 00000000000..88390664c1c --- /dev/null +++ b/docs/5.3-Migrating-to-Project-Reactor.md @@ -0,0 +1,40 @@ +# Migrating to Project Reactor + +## Motivation + +The two main reasons we are adopting Reactor for our concurrent operations: + +1. Using a consistent API helps with thread management, making it easier to allocate the right amount of threads and clean them up when we need to. +2. The Flux API offered by Project Reactor provides better ways to define and schedule asynchronous operations than the standard Java API does. + +## Deprecations + +- The creation of threads and threadpools is something that should be mediated by the engine. +Modules should not be using `new Thread()` directly. +- [o.t.e.utilites.concurrency.TaskMaster](https://github.com/MovingBlocks/Terasology/blob/v5.2.0/engine/src/main/java/org/terasology/engine/utilities/concurrency/TaskMaster.java) and its related `Task` class. + +## New Interfaces + +Most [Project Reactor](https://projectreactor.io) classes (from Reactor Core, Reactor Extra, and Reactor Test) are available to modules. +The [Reactor Reference Guide](https://projectreactor.io/docs/core/release/reference/) includes an introduction to them. +(It _occasionally_ assumes familiarity with the Reactive Streams specification, but its examples are complete on their own.) + +The [o.t.e.core.GameScheduler](https://jenkins.terasology.io/teraorg/job/Terasology/job/engine/job/develop/javadoc/org/terasology/engine/core/GameScheduler.html) class provides methods for scheduling work +or obtaining a [Scheduler](https://projectreactor.io/docs/core/release/reference/#schedulers) instance. + +## Migration Guide + +### TaskMaster + +Replacements for TaskMaster methods: + +* `TaskMaster.create*(name, threads)`: Use `GameScheduler` to use an existing `Scheduler` instance. +We do not expect modules need to create new Schedulers. +* `Task`: [Runnable](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Runnable.html). (Runnable is a Functional Interface in Java; any zero-argument method may be passed anywhere a Runnable is expected.) +* `task.getName()`: Pass a name to `GameScheduler.scheduleParallel(name, task)`. +* `tm.offer(task)`: `GameScheduler.scheduleParallel(name, task)`. +* `tm.put(task)`: This is a blocking method; no direct replacement is offered. +Replace it with an asynchronous method, perhaps using Reactor's [Mono](https://projectreactor.io/docs/core/release/reference/#mono) to schedule work to continue after its completion. + +The above methods are only the ones that most directly correspond to the old TaskMaster interface. +You will likely want to take advantage of other [Flux operations](https://projectreactor.io/docs/core/release/reference/#which-operator) to use the results of asynchronous methods and handle errors. diff --git a/docs/Advanced-Options.md b/docs/Advanced-Options.md new file mode 100644 index 00000000000..405841efb92 --- /dev/null +++ b/docs/Advanced-Options.md @@ -0,0 +1,36 @@ +The `Terasology` command has some options to control its initial configuration. Run `Terasology -h` for a list. Some of the options are documented in more detail below. + + +## Memory Usage + + \ No newline at end of file diff --git a/docs/Block-Definitions.md b/docs/Block-Definitions.md new file mode 100644 index 00000000000..91344767905 --- /dev/null +++ b/docs/Block-Definitions.md @@ -0,0 +1,183 @@ +New blocks can easily be included in the game by creating a module with a `.block` block definition file. This article gives an overview of what can be specified in these files. + +# What exactly does a block definition produce? + +There are two things that are generated by block definitions - blocks themselves, with their specific settings, shapes and rotations, and block families which are sets of blocks that are considered to be variations of the same block. An example is stone stairs. Each possible rotation of the stairs is a separate block, but when you pick them up they are considered the same and stack. + +If a block doesn't define a shape, or defines multiple shapes, then it is made available as a full block via the uri "moduleName:blockName", where the moduleName is the id of the containing module or "engine" for built-in blocks, and the blockName is the filename of the block (ignoring the extension). It can also be requested in any shape by using the uri "blockModule:blockName:shapeModule:shapeName". + +If a block has a shape defined it can only be requested using the block uri, and its shape is fixed. + +# General structure +The block definition files are structures as any other JSON document. For information see the JSON specification. + +# Options + +## Inheritance + +Block definitions can extend from other block definitions, specifying just the features by which they differ. This simplifies creating classes of block (like plants). + + | Option | Value(s) | Default | Description | + | ------------ | :--------------------------------------------: | :-----: | --------------------------------------------------------------------- | + | **basedOn** | _A block definition uri (e.g. "engine:plant")_ | | Specifies the block to base this block on. | + | **template** | _true, false_ | false | If true, this block cannot be created and exists only to be based on. | + +## Informational + + | Option | Value(s) | Default | Description | + | --------------- | :------: | :-----------------------------------------------------------: | ----------------------------------------------------------------------------------------------------------------- | + | **displayName** | | The file name of the block, with the first letter capitalised | The name of the block that is shown to players - particularly when the block is picked up and in their inventory. | + +## Core behavioural + + | Option | Value(s) | Default | Description | + | ---------------------- | :-----------: | :-----: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | + | **attachmentAllowed** | _true, false_ | true | Determines whether other blocks can be attached to this block. | + | **hardness** | \ | 3 | Specifies the hardness of the block - effectively its health. | + | **liquid** | _true, false_ | false | Determines if the block is a liquid. | + | **replacementAllowed** | _true, false_ | false | Specifies whether the block can be replaced freely by other blocks - that you can place another block over it. **In order to make a block replaceable, it requires the block not to be targetable!** | + | **supportRequired** | _true, false_ | false | Specify whether the block should be destroyed when no longer attached to any other block. **Only works for vertically adjacent blocks - e.g. grass is removed if the ground under it is destroyed** | + +## Tiles +By default, a block will try to use a tile texture with a matching filename. e.g. A block defined in Grass.block will use the block tile Grass.png from the same module. + +You can specify a different tile to be used with the "tile" property: + + "tile" : "engine:grass" + +You can also use different texture tiles for the different sides of the block. To do so, +you have to name the corresponding tiles in a `tiles` section of the block definition, e.g. for the chest block: + + "tiles" : { + "sides" : "core:ChestSides", + "front" : "core:ChestFront", + "topBottom" : "core:ChestTopBottom" + } + +Possible block parts are + * **all** to change every tile (same as using the "tile" property) + * **topBottom** to change the top and bottom tile + * **sides** to change the four horizontal sides (excluding only top and bottom) and can itself be overridden + * **front, left, right, back, top, bottom** refer to the specific sides + * **center** to change the tile on the center part of the block (see the section on shapes_ + +| Option | Value(s) | Default | Description | +| ----------------- | :---------------: | :------------------------------------------------------------: | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **tile** | _a blockTile uri_ | A block tile with the same module and name as block definition | Specifies what blockTile to use to texture this block | +| **tiles** | | | Allows the blockTile used by different parts/sides of the block to be overridden. | +| **doubleSided** | _true, false_ | false | Whether this block should be rendered double sided. This done for billboard plants to render both sides of polygons. | +| **invisible** | _true, false_ | false | If set to `true` the block is not rendered at all. | +| **translucent** | _true, false_ | false | Determines whether the block is transparent/translucent or not. Blocks with this option enabled can use textures with transparency (but not translucency, see _ice_). Moreover, translucent blocks do not prevent occluded blocks behind them from beeing rendered (blocks behind a translucent glass block are still displayed). | +| **ice** | _true, false_ | false | Determines whether the block is translucent, but not completely transparent. Blocks with this option enabled can use textures with translucency. Blocks with this enabled should also have _translucent_ enabled, or they will occlude blocks behind them. | +| **shadowCasting** | _true, false_ | true | Should this block cast a slight shadow around it? | +| **waving** | _true, false_ | false | Whether the blocks waves in the wind. Mainly used for grass and foliage. | +| **luminance** | _\_ | 0 | The light level of the block. The default torches have a light value of 15, for reference. | + +### Color Lookup tables (LUTs) +Color gradients can be used to change the color of specific blocks, e.g. grass or fooliage. + + | Option | Value(s) | Description | + | ---------------- | :-----------: | ------------------------------------------------------------------------------------------------------ | + | **colorSource** | _\_ | e.g. `"colorSource" : "color_lut"`. | + | **colorSources** | | Enumerate the different color sources, `default` can be used to exclude LUTs for specific block parts. | + | **colorOffset** | [R, G, B, A ] | Specify a color offset, e.g. given for red leaves: `"colorOffset" : '[2.0, 0.0, 0.5, 1.0']`. | + +## Shapes and Rotation + +Block shapes are specialised meshes that give a block its shape. If no shape is specialised then a block is a cube. + +Generally for a non-cubic block you would define the shape with the shape property. + + "shape" : "engine:cube" + +You can also use the "shapes" property to list a number of valid shapes. + +When using a block shape, it is often desirable to allow the block to rotate based on how it is being placed, or even to have different shapes depending on how it is being placed. This can be enabled using the "rotation" property. The following settings are available: + + * **none** The block will not be rotated (default) + * **horizontal** The block will rotate on the horizontal plane. For instance, stairs will face towards the player when placed. + * **alignToSurface** Similar to horizontal but with support for different blocks when placed against the ground or ceiling. + +When using the alignToSurface rotation mode, you can specify a "sides", "top" and/or "bottom" section to provide override properties for those placements. e.g. + + "rotation" : "alignToSurface", + "sides" : { + "shape" : "engine:stair", + }, + "bottom": { + "shape" : "engine:cube" + } + +would be shaped like a cube when placed on the ground, and shaped like stairs when placed against a side. It cannot be placed against a ceiling (as no shape is defined for that case). Many properties can be overridden in this manner. + +| Option | Value(s) | Default | Description | +| ---------------- | :--------------------------------: | :-----------: | ------------------------------------------------------------------------------------------------ | +| shape | _a shape uri_ | "engine:cube" | The shape of the block | +| shapes | _a lists of shape uris_ | | A set of valid shapes for this block | +| rotation | _none, horizontal, alignToSurface_ | none | Defines the rotation mode for the block | +| top/bottom/sides | | | In alignToSurface rotation mode, allows settings to be specified for specific surface placements | + +For more on shapes see [Block Shapes](Block-Shapes.md) + +## Collision related + +| Option | Value(s) | Default | Description | +| -------------- | :-----------: | :-----: | ------------------------------------------------------------------------------------------------------------------ | +| **penetrable** | _true, false_ | false | A block is penetrable if it does not block solid objects. | +| **targetable** | _true, false_ | true | Define whether the block can be targeted for interactions. **Must be set to `false` to allow direct replacement.** | + +## Physics related +| Option | Value(s) | Default | Description | +| ------------------- | :-----------: | :-----: | ------------------------------------------------------------------------------------------------------------ | +| **debrisOnDestroy** | _true, false_ | true | If enabled destroyed blocks will drop a miniature instance of the block that can be picked up by the player. | +| **mass** | _\_ | 10 | The mass value for the physics simulation. | + +> The mass does not seem to have any influence on the objects in the game. + +## Entity integration +The options for enity integration are wrapped in the `entity` option, e.g. for a chest: + + "entity" : { + "prefab" : "core:chest", + "mode" : "persistent" + } + +| Option | Value(s) | Default | Description | +| ---------- | :--------------: | :-----------: | ----------------------------------------------- | +| **prefab** | _\_ | | The corresponding entity prefab for this block. | +| **mode** | _\_ | onInteraction | Specify the mode for the entity. | + +## Inventory settings +The inventory settings have to be in an `inventory` section as well, e.g. again for the chest definition: + + "inventory" : { + "stackable" : false, + "directPickup" : true + } + +| Option | Value(s) | Default | Description | +| ---------------- | :-----------: | :-----: | ---------------------------------------------------------------------------------- | +| **directPickup** | _true, false_ | false | Whether this block should go directly into a character's inventory when harvested. | +| **isStackable** | _true, false_ | true | Determines whether the block type is stackable in the inventory. | + +## Mesh related + +| Option | Value(s) | Default | Description | +| ---------- | :---------------: | :-----------: | -------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **shape** | _\_ | "engine:cube" | Define the shape of the block. You can use either existing shapes or use self created ones. For more information, see [Block Shapes](Block-Shapes.md). | +| **shapes** | _[\,...]_ | | You can restrict the usage of a block type to some shapes. If not explicitly defined, a block type can be instantiated as any available shape. | + +## Block Families/Categories + + | Option | Value(s) | Description | + | -------------- | :--------------------: | -------------------------------------------------------------------------------------------------------------- | + | **categories** | _\_ | Give a list of categories the block belongs to, e.g. new soil types might go into `"categories" : '["soil"']`. | + +# Temporary/deprecated + + | Option | Value(s) | Default | Description | + | -------------- | :-----------: | :-----: | ---------------------------------------------------------------------------- | + | **craftPlace** | _true, false_ | true | Determines whether the player can open up the crafting system on this block. | + +# Suggestions +* Add soundfile specification for walking sounds on specific block types? diff --git a/docs/Block-Shapes.md b/docs/Block-Shapes.md new file mode 100644 index 00000000000..b8c3e6fb5f2 --- /dev/null +++ b/docs/Block-Shapes.md @@ -0,0 +1,149 @@ +

+Block Shapes +

+ +A block shape defines a way a block can look - its shape. +Each block shape can be used by multiple blocks, each applying a different texture to it. +Each shape is composed of _up to_ 7 parts: + +- The **center mesh**, which is always rendered if present (visible). This is typically used for parts of the block contained _within_ the area of the block. +- Six **side meshes** - one for each direction. These are only rendered if the side is not obscured. + +Additionally, each side is either a full side or a partial side. +A full side fills the entire side of the block, and thus obscures the sides of adjacent blocks. +A partial side does not. + +Below is an example stair block shape, with each side moved away from the center. +In the stair block, the Back and Bottom sides are full sides, while the Top, Front, Left and Right sides are not. + +![An 'exploded' block shape](block-shapes-exploded.png) + +## Shape part + +Surrounds the shape definition. +Contains one or more Mesh Part blocks, which may be named Center, Top, Bottom, Left, Right, Front or Back. +These correspond to each of the direction, and the central mesh as follows: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Sub-blockDirection
Center-
Top+Y axis
Bottom-Y axis
Front-Z axis
Back+Z axis
Left-X axis
Right+X axis
+ +> ℹ️ A standard block is centered on the origin, with each side 0.5 units away in the appropriate direction. + +Generally the front of the block is the side you expect to face towards the player when they are placing it, e.g., the front of stairs such that you can walk upwards. +Additionally a Shape may contain a [Colliders](#colliders) section, if it wishes to have a collision shape other than the full block. +Stairs, for instance, have two colliders - one for the bottom step and one for the top. + +## Mesh Part + +Each mesh part block contains the following components: + +- **vertices** - a list of 3D vectors that make up the mesh part +- **normals** - a list of normals corresponding to the vertices +- **texCoords** - the texture coordinates corresponding to the vertices +- **faces** - one or more lists of indices corresponding to vertices, each of which forms a polygon that comprises the mesh part. +- **fullSide** - a boolean denoting whether a side obscures all adjacent sides or not - basically whether it is a square the fills that side of the cube. This isn't used by the center mesh part, and if not specified defaults to false. + +## Colliders + +The colliders section lists one or more Axis-Aligned Boxes that define collision for the shape. +Axis aligned means the sides of the box are always at right angles to the primary axes. + +Each collider has two properties: + +- **position** - where the collider is centered +- **extents** - how far away each corner of the box is along each axis. + +## Block Shapes in Blender + +Terasology has a easy-to-edit shape format for blocks allowing anyone, with a small amount of learning, to create new block shapes that can be easily used in the game. +While the block shape format _is_ hand editable and you _could_ write a block shape by hand, there is a great addon for the free, open source 3d editor Blender ([Blender's Home](https://www.blender.org)) that allows you to easily create block shapes in a visual WYSIWYG environment and export them for use in Terasology. + +### Install the Block Shape Addon + +- **Download Blender**. We recommend Blender 2.63 to be used (there is experimental support for Blender 2.8). +- **Download the shape plugin** from [MovingBlocks/BlenderAddon](https://github.com/MovingBlocks/BlenderAddon/releases). + There are two zips that can be downloaded one is for the md5 exporter and the other shape exporter is used for exporting block shapes. + + > Note: sometimes the shape exporting addon can be buggy, if it does not import properly extract the zip and place it in the plugins folder. + > The addon should be visible in the _Settings_ menu. + +- _Optional:_ additional example shapes can be found [here](https://github.com/MovingBlocks/BlenderAddon/tree/master/examples/shapes) + +### Fundamentals + +A block shape in Blender is a set of mesh objects corresponding to the various parts of the block shape. +For each side and the center part of the block shape, a mesh object with the corresponding name can be present: Top, Bottom, Left, Right, Front, Back and Center. +Additionally, extra mesh objects can be used to define the collision bounds for the block shape. + +When creating a block shape, you need to keep the following in mind: + +- Blocks should be created centered on the origin +- A standard block is half the scale of a new Blender cube +- Blender axes are different from Terasology's axes, see [Shape Part](#shape-part). + +### Terasology Exporter Addon Properties + +The Terasology Block Shape addon adds two panes to the 3d view properties side panel in Blender (by default the shortcut is N while hovering over the 3D view window). + +The first pane, Terasology Scene Properties, contains settings that are universal (not based on what mesh you have selected): + +- Author - Your name here +- Collision Type +- Is Collision Symmetric - Is collection unchanged if the block is rotated? If checked, then definitions using this shape will not have a block generated for each rotation. +- Use Billboard Normals - For flat, vertical billboards, this causes the normals to point upwards so they are lit correctly by sunlight. + +The second pane, Terasology Mesh Properties, contains settings that apply to the currently selected mesh object: + +- Full Side - Does this side fill the block's space - this will cause the side of blocks facing it not to be rendered. +- Collider Type + +Example properties screen (may be outdated): + +![Properties window](block-shapes-TerasologyProperties.png) + +### Tips & Tricks + +- To avoid problems later in the creation process, scale in Edit Mode instead of Object Mode. +- When UV mapping, you should map against a single 16x16 texture. +- To preview your shapes texture after unwrapping, you can switch a 3d view in Blender to Textured shading. + Additionally, having mipmapping turned off will give a display very similar to what you will see in-game. + To disable mipmapping: + - Go to the user preferences (File menu). + - In the user preferences window, go to the 'System' tab. + - In the middle near the top you should find a checked option that says 'Mipmaps'. Uncheck this. + +### Related Links and Resources + +- [Youtube series covering block shape creation in Blender](http://www.youtube.com/watch?v=BM219wj0v6Y) diff --git a/docs/Build-Setup.md b/docs/Build-Setup.md new file mode 100644 index 00000000000..8f555f5da98 --- /dev/null +++ b/docs/Build-Setup.md @@ -0,0 +1,20 @@ +In Terasology, the engine as well as all libraries and modules are automatically built on [Jenkins](https://jenkins.terasology.io) and the resulting build artifacts published to our [Artifactory](http://artifactory.terasology.org). +While libraries and the engine define how they are built via a `Jenkinsfile` and `build.gradle` in their repositories, the modules in the [Terasology GitHub Org](https://github.com/Terasology) do not (yet). + +![Terasology - Build Setup](images/Build-Setup.png) + +## Local Module Builds + +In a local setup, the [template for modules' `build.gradle`](https://github.com/MovingBlocks/Terasology/blob/develop/templates/build.gradle) located in the engine repo, is copied to the local source of the module(s) to be built. +If the should be deleted, corrupted or out-of-sync, it can be re-synchronized using `groovyw module refresh`. + +## CI Module Builds + +For the CI, the [`Jenkinsfile` in `ModuleJteConfig`](https://github.com/MovingBlocks/ModuleJteConfig/blob/develop/Jenkinsfile) defines how modules are built. +As modules normally need to be built while being embedded in a full engine workspaces, when being built in the CI, they are "on their own". +To enable building modules "standalone", the so called _build harness_ is copied into the build environment. + +The build harness is a collection of files that are attached to engine jobs. +It provides the gradlew wrapper (`gradlew`) to execute the build, files defining the build logic, amongst others the `build.gradle` as well as additional files required for building modules such as the natives and config files for the analytics (code quality checks, etc.). +The build harness files are generated as part of the [engine job's build stage](https://github.com/MovingBlocks/Terasology/blob/develop/Jenkinsfile#L53-L61) and are copied into the build environment as part of the [build setup stage](https://github.com/MovingBlocks/ModuleJteConfig/blob/develop/Jenkinsfile#L36-L37). + diff --git a/docs/Character-Module.md b/docs/Character-Module.md new file mode 100644 index 00000000000..6732e30276e --- /dev/null +++ b/docs/Character-Module.md @@ -0,0 +1,102 @@ +- replace monkey head with animated cube + - create animated model in Blender and export it + - Create particle emitter so that there are particles on movement + - Remove monkey head +- create character module that if active shows a different character + - e.g. monkey head + - create hook for character module(s) to place character +- Add model that plays walk animation and idle animations at proper times. + - Add model with walk and idle animation (e.g. skeleton from gooey's quest or another model) + - create hooks for walk and idle animations - to be handled by the character module +- Split up body + - Create separate animated models for head, chest, legs and arms + - Integrate body parts with existing animations for walk and idle +- Head customization + - Add further body parts (eye, beard, hair) attached to the head. + - Introduce component that has fields like hairColor, hairModel, beardColor, beardModel, mouthModel, mouthColor, innerEyeModel, innerEyeColor, outerEyeColor, outerEyeModel +- Add generic way to express animation wishes to generic character module (as module, in engine, in character module) + + + +``` +{ + "location": {}, + "particleDataSprite": { + "texture": "white" + }, + "energyRangeGenerator": { + "minEnergy": 0.5, + "maxEnergy": 1.5 + }, + "velocityRangeGenerator": { + "minVelocity": [0.0, 0.0, 0.0], + "maxVelocity": [0.0, 0.0, 0.0] + }, + "velocityAffector": {}, + "particleEmitter": { + "lifeTime": 72000, + "particleSpawnsLeft": 10000, + "maxParticles": 10000, + "particleCollision": false, + "destroyEntityWhenDead": true, + "spawnRateMin":20, + "spawnRateMax":20 + + }, + "PositionRangeGenerator": { + "minPosition": [-0.3, -0.3, -0.3], + "maxPosition": [0.3, 0.3, 0.3] + }, + "ScaleRangeGenerator": { + "minScale": [0.03, 0.03, 0.03], + "maxScale": [0.03, 0.03, 0.03] + }, + "ColorRangeGenerator": { + "minColorComponents": [1.0, 0.0, 0.0, 0.5], + "maxColorComponents": [1.0, 1.0, 0.0, 0.5] + + }, + "Network": {} +} +``` + +``` +/** + * Attaches a particle emitting component to the entity as owned child that is not persistent + */ +public class AttachParticleEmitterComponent implements Component { + /** + * Changes of this field at runtime are not supported yet + */ + public Prefab particleSystem; +} + +``` + + + +``` + +/** + * Logic for {@link AttachParticleEmitterComponent} + */ +@RegisterSystem(RegisterMode.AUTHORITY) +public class AttachParticleEmitterSystem extends BaseComponentSystem { + + @In + EntityManager entityManager; + + @ReceiveEvent + public void onAttachmentNeeded(OnActivatedComponent event, EntityRef owningEntity, LocationComponent ownerLocationComponent, + AttachParticleEmitterComponent attachComponent) { + EntityBuilder entityBuilder = entityManager.newBuilder(attachComponent.particleSystem); + entityBuilder.setPersistent(false); + LocationComponent locationComponent = new LocationComponent(); + entityBuilder.addOrSaveComponent(locationComponent); + entityBuilder.setOwner(owningEntity); + Vector3f offset = new Vector3f(); + EntityRef particleSystemEntity = entityBuilder.build(); + Location.attachChild(owningEntity, particleSystemEntity, offset, new Quat4f(1, 0, 0, 0)); + } +} +``` \ No newline at end of file diff --git a/docs/Code-Conventions.md b/docs/Code-Conventions.md new file mode 100644 index 00000000000..ba2146fe1db --- /dev/null +++ b/docs/Code-Conventions.md @@ -0,0 +1,293 @@ +Over the course of its life, every project develops it's own code conventions, and Terasology is no different. +Note that we try and stick to the standard Java conventions as much as possible. + +- [Indentation](#indentation) +- [Naming Conventions](#naming-conventions) +- [Starred Imports](#starred-imports) +- [Others](#others) +- [Checkstyle](#checkstyle) +- [Testing](#testing) +- [JavaDoc](#javadoc) + +## Indentation + +We follow the [1TBS](https://en.wikipedia.org/wiki/Indentation_style#Variant:_1TBS_.28OTBS.29) (One true brace style) exclusively. Also, every text file (be it code, or text-asset like json) should end with an empty line. + +For all code files, we follow a **4-space** indentation style, and firmly believe in the saying "death to all tabs". + +```java +void someFunction() { + doSomething(); + doSomethingElse(); +} +``` + +However, for asset files (like JSON, that is used for all prefab and config files) we follow a **2-space** indentation style. + +```json +container: { + property1: "someValue", + property2: "someOtherValue" +} +``` + +Note that even for single line `if` and `while` statements, we use brackets. + +```java +// Bad +if (something) + // Do something + +// Good +if (something) { + // Do something +} +``` + +## Naming Conventions + +Almost everything uses either camelCase, or PascalCase (same as camelCase, but with the first letter capitalized). + +| Type | Style to follow | +| ------------------------ | --------------- | +| Package name | camelCase | +| Class or interface name | PascalCase | +| Functions and attributes | camelCase | +| Constants | ALL_CAPITAL | + +For more information, refer to the [official Oracle docs](http://www.oracle.com/technetwork/java/codeconventions-135099.html). + +```java +package test; + +class TestClass { + private static final int SOME_CONSTANT = 0; + + int someInt; + + void doSomething() { + } +} +``` + +### Descriptive Variable Names + +> ℹ️  _**Almost all variables should have descriptive names**_ + +A single letter variable is almost always not a descriptive name. This means that a developer should be able to isolate a proportion of the code and have a rough understanding of what each variable does. This does not require long and complicated names, but primarily means that a variable should be a complete word. + +There are reasonable exceptions to this such as using `i` as a variable in a `for` loop. + +## Starred Imports + +> ⚠️ **_Strictly avoid star imports in all cases_** + +A number of IDE's by default collapse imports into the star format. For instance, a collection of imports such as + +```java +import java.util.List; +import java.util.Set; +import java.util.LinkedList; +import java.util.Locale; +import java.util.Optional; +import java.util.Arrays; +``` + +would be condensed down to + +```java +import java.util.*; +``` + +Whilst this is shorter code, it has a few drawbacks. For these reasons we strictly avoid star imports. + +Of note is that IntelliJ is known to not behave nicely when it comes to disabling this. [There are details on how to disable it in this stack overflow QA](https://stackoverflow.com/questions/3587071/disable-intellij-starred-package-imports). + +## Others + +- Use `// TODO` comments to note where something needs to be done +- Don't use Hungarian notation to denote types +- Use interfaces over concrete classes for variables (e.g. `Map` instead of `HashMap`). + +## Checkstyle + +We use the [Checkstyle](http://checkstyle.sourceforge.net/) project to help keep the codebase consistent with some code conventions. +The Terasology engine and all modules use the same configuration as defined in [MovingBlocks/TeraConfig](https://github.com/movingblocks/teraconfig). +You can find the local copy of the configuration file at `config/metrics/checkstyle/checkstyle.xml`. + +When working an area please keep an eye out for existing warnings to fix in files you're impacting anyway. +Make sure to run Checkstyle on any new files you add since that's the best time to fix warnings. + +### Gradle + +You can easily run Checkstyle via the Gradle wrapper `gradlew`. +See [Checkstyle Gradle Plugin](https://docs.gradle.org/current/userguide/checkstyle_plugin.html) for more information. + +- `gradlew check` to run all checks and tests (incl. Checkstyle) +- `gradlew engine:checkstyleMain` to run Checkstyle for just the engine's main source directory + +### IntelliJ Integration + +We recommend to use the [Checkstyle IDEA Plugin](https://plugins.jetbrains.com/plugin/1065-checkstyle-idea). +IntelliJ IDEA allows to easily run Checkstyle checks for the whole project, specific folders, or just single files. + +In case the IDE does not pick put the correct Checkstyle rules automatically you may have to configure it manually. +The Checkstyle configuration file is located at `config/metrics/checkstyle/checkstyle.xml`. + +### Jenkins Integration + +Checkstyle statistics are automatically calculated per build and turned into nifty metrics per build. +For instance, have a look at the [Checkstyle Report for the engine](http://jenkins.terasology.io/teraorg/job/Terasology/job/engine/job/develop/checkstyle). + +## Testing + +Terasology's test suite can be found in the [engine-tests/src/test/java/org/terasology](https://github.com/MovingBlocks/Terasology/tree/develop/engine-tests/src/test/java/org/terasology) folder. + +### Why Test? + +Most developers spend the majority of their time not *writing* code, but debugging and maintaining it. Unit tests are one of the best ways to minimize unnecessary time spent on both. Testing also helps document your code. Finally, we use a passing unit test suite as one of the criteria for accepting pull requests. + +### What software is used to test? + +Terasology uses [JUnit 5](http://www.junit.org/) for its automated test suite. It also uses [Mockito](http://site.mockito.org) for mocking/stubbing in special situations for which a dependency is too expensive or unreliable to bring into a test suite - for example, network activity or OpenGL. + +An IDE is highly encouraged for running tests, as most support JUnit. In Eclipse, for example, you can quickly run a single test by right-clicking on the test method declaration and selecting *Run As → JUnit Test*. You can give this command a shortcut key of your choice to make it even faster. + +### How often should I use Mocks or Stubs? + +Rarely. If you find yourself using Mockito a lot, you may want to consider refactoring your code to be more modular. + +### What should I test? + +Ideally, every line of code that you want to merge should be backed by a unit test. However, there are exceptions, such as straightforward getters/setters. The general rule is that the more likely your code is to fail or change, the more important it is to have test coverage. + +### When should I run the full test suite? + +On pulling any changes and before making any pull requests. To save yourself any unpleasant surprises, you should also run the complete test suite after completing any unit of work on the code. + +### How should I test? + +Ideally, you should [write your tests first](http://en.wikipedia.org/wiki/Test-driven_development). Begin by thinking about what success looks like for the problem you're trying to solve. Then attempt to write a test in code that captures the solution. Then write the code to make the test pass. + +## JavaDoc + +Our Javadoc guidelines are loosely based on Stephen Colebourne's blog article on [Javadoc coding standards](http://blog.joda.org/2012/11/javadoc-coding-standards.html) and [Oracle's guide on writing Javadoc](http://www.oracle.com/technetwork/java/javase/documentation/index-137868.html). Note that these guidelines merely specify the layout and formatting of the documentation but not its content. + +A Javadoc comment is written in HTML and can therefore use common HTML tags. A doc comment is made up of two parts, the description followed by block tags. Keep in mind that often as not Javadoc is read in its source form, so it should be easy to read and understand without the generated web frontend. + +### First Sentence + +Write the first sentence as a short summary of the method, as Javadoc automatically places it in the method summary table (and index). This first sentence, typically ended by a dot, is used in the next-level higher Javadoc. As such, it has the responsibility of summing up the method or class to readers scanning the class or package. To achieve this, the first sentence should be clear and punchy, and generally short. + +While not required, it is recommended that the first sentence is a paragraph to itself. This helps retain the punchiness for readers of the source code. + +It is recommended to use the third person form at the start. For example, "Gets the foo", "Sets the "bar" or "Consumes the baz". Avoid the second person form, such as "Get the foo". + +### HTML Tags + +As a rule-of-thumb use the [standard format for doc comments](http://www.oracle.com/technetwork/java/javase/documentation/index-137868.html#format) with plain HTML tags (no XHTML). For longer doc comments use `

` to separate different paragraphs. Note that you are not allowed to use self-closing tags, e.g., `

`. Place a single `

` tag on the blank line between paragraphs: + +```java + /** + * First paragraph. + *

+ * Second paragraph. + * May be on multiple lines. + *

+ * Third paragraph. + */ + public ... +``` + +Lists are useful in Javadoc when explaining a set of options, choices or issues. These standards place a single `

  • ` tag at the start of the line and no closing tag. In order to get correct paragraph formatting, extra paragraph tags are required: + +```java + /** + * First paragraph. + *

  • +
    --max-data-size=N
    +

    Enforced by the operating system instead of the Java Virtual Machine, this limits memory usage in a different way than setting Java's maximum heap size (the -Xmx java option). +Use this to prevent Terasology from gobbling all your system memory if it has a memory leak. + +Set this limit to a number larger than the maximum java heap size. +It is normal for a process to need some additional memory outside the java heap. + +This value is in bytes, such as `2048M` or `4.7GB`. + +This is currently only implemented on Linux. + +On Windows, you may be able to set a limit using one of these external tools: + - Application Verifier (AppVerif.exe), available from the Windows SDK + - Process Governor (procgov), an open source third-party tool + +

    +
    --oom-score=N
    +

    Make the Linux Out-of-Memory killer more likely to pick Terasology. + +When a Linux system runs out of available memory, it invokes the Out of Memory killer (aka OOM killer) to choose a process to terminate to free up some memory. + +Add **N** to this score if you want to make Terasology a bigger target. +Why? If you'd rather the game process be the thing that gets killed instead of some other memory-hungry program, like your browser or IDE. +A [score][proc5] of 1000 is equivalent to saying “this process is taking all the memory.” + +This out-of-memory score is a Linux-specific mechanism. + +[proc5]: https://man7.org/linux/man-pages/man5/proc.5.html#:~:text=/proc/%5Bpid%5D/-,oom_score_adj,-(since +

    +