Package org.terasology.persistence.internal

Source Code of org.terasology.persistence.internal.StorageManagerInternal

/*
* Copyright 2013 MovingBlocks
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.terasology.persistence.internal;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

import gnu.trove.iterator.TIntIterator;
import gnu.trove.map.TIntObjectMap;
import gnu.trove.map.hash.TIntObjectHashMap;
import gnu.trove.set.TIntSet;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.terasology.config.Config;
import org.terasology.engine.ComponentSystemManager;
import org.terasology.engine.module.ModuleManager;
import org.terasology.engine.paths.PathManager;
import org.terasology.entitySystem.entity.EntityRef;
import org.terasology.entitySystem.entity.internal.EngineEntityManager;
import org.terasology.entitySystem.entity.internal.EntityDestroySubscriber;
import org.terasology.entitySystem.systems.ComponentSystem;
import org.terasology.game.Game;
import org.terasology.game.GameManifest;
import org.terasology.logic.location.LocationComponent;
import org.terasology.math.AABB;
import org.terasology.math.TeraMath;
import org.terasology.math.Vector3i;
import org.terasology.module.Module;
import org.terasology.module.ModuleEnvironment;
import org.terasology.monitoring.PerformanceMonitor;
import org.terasology.network.Client;
import org.terasology.network.ClientComponent;
import org.terasology.network.NetworkSystem;
import org.terasology.persistence.ChunkStore;
import org.terasology.persistence.PlayerStore;
import org.terasology.persistence.StorageManager;
import org.terasology.persistence.serializers.PrefabSerializer;
import org.terasology.protobuf.EntityData;
import org.terasology.registry.CoreRegistry;
import org.terasology.utilities.FilesUtil;
import org.terasology.utilities.concurrency.ShutdownTask;
import org.terasology.utilities.concurrency.Task;
import org.terasology.utilities.concurrency.TaskMaster;
import org.terasology.world.WorldProvider;
import org.terasology.world.biomes.Biome;
import org.terasology.world.biomes.BiomeManager;
import org.terasology.world.block.BlockManager;
import org.terasology.world.block.family.BlockFamily;
import org.terasology.world.chunks.Chunk;
import org.terasology.world.chunks.ChunkProvider;
import org.terasology.world.chunks.internal.ChunkImpl;

import javax.vecmath.Vector3f;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.ConcurrentMap;
import java.util.zip.GZIPInputStream;

/**
* @author Immortius
* @author Florian <florian@fkoeberle.de>
*/
public final class StorageManagerInternal implements StorageManager, EntityDestroySubscriber {
    private static final Logger logger = LoggerFactory.getLogger(StorageManagerInternal.class);

    private final TaskMaster<Task> saveThreadManager;

    private ModuleEnvironment environment;
    private EngineEntityManager entityManager;
    private PrefabSerializer prefabSerializer;

    private TIntObjectMap<List<StoreMetadata>> externalRefHolderLookup = new TIntObjectHashMap<>();
    private Map<StoreId, StoreMetadata> storeMetadata = Maps.newHashMap();

    private boolean storeChunksInZips = true;
    private final StoragePathProvider storagePathProvider;
    private final SaveTransactionHelper saveTransactionHelper;
    private SaveTransaction saveTransaction;
    private Config config;

    /**
     * Time of the next save in the format that {@link System#currentTimeMillis()} returns.
     */
    private Long nextAutoSave;
    private boolean saveRequested;
    private ConcurrentMap<Vector3i, CompressedChunkBuilder> unloadedAndUnsavedChunkMap = Maps.newConcurrentMap();
    private ConcurrentMap<Vector3i, CompressedChunkBuilder> unloadedAndSavingChunkMap = Maps.newConcurrentMap();
    private ConcurrentMap<String, EntityData.PlayerStore> unloadedAndUnsavedPlayerMap = Maps.newConcurrentMap();
    private ConcurrentMap<String, EntityData.PlayerStore> unloadedAndSavingPlayerMap = Maps.newConcurrentMap();

    public StorageManagerInternal(ModuleEnvironment environment, EngineEntityManager entityManager) {
        this(environment, entityManager, true);
    }

    public StorageManagerInternal(ModuleEnvironment environment, EngineEntityManager entityManager, boolean storeChunksInZips) {
        this.entityManager = entityManager;
        this.environment = environment;
        this.storeChunksInZips = storeChunksInZips;
        this.prefabSerializer = new PrefabSerializer(entityManager.getComponentLibrary(), entityManager.getTypeSerializerLibrary());
        entityManager.subscribe(this);
        this.storagePathProvider = new StoragePathProvider(PathManager.getInstance().getCurrentSavePath());
        this.saveTransactionHelper = new SaveTransactionHelper(storagePathProvider);
        this.saveThreadManager = TaskMaster.createFIFOTaskMaster("Saving", 1);
        this.config = CoreRegistry.get(Config.class);

    }

    @Override
    public void finishSavingAndShutdown() {
        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();
        }
    }

    /**
     *
     * @param unsavedEntities currently loaded persistent entities without owner that have not been saved yet.
     */
    private void addGlobalStoreToSaveTransaction(SaveTransactionBuilder saveTransaction, Set<EntityRef> unsavedEntities) {
        GlobalStoreSaver globalStoreSaver = new GlobalStoreSaver(entityManager, prefabSerializer);
        for (StoreMetadata table : storeMetadata.values()) {
            globalStoreSaver.addStoreMetadata(table);
        }
        for (EntityRef entity : unsavedEntities) {
            globalStoreSaver.store(entity);
        }
        EntityData.GlobalStore globalStore = globalStoreSaver.save();
        saveTransaction.addGlobalStore(globalStore);
    }

    @Override
    public void loadGlobalStore() throws IOException {
        Path globalDataFile = storagePathProvider.getGlobalEntityStorePath();
        if (Files.isRegularFile(globalDataFile)) {
            try (InputStream in = new BufferedInputStream(Files.newInputStream(globalDataFile))) {
                EntityData.GlobalStore store = EntityData.GlobalStore.parseFrom(in);
                GlobalStoreLoader loader = new GlobalStoreLoader(environment, entityManager, prefabSerializer);
                loader.load(store);
                for (StoreMetadata refTable : loader.getStoreMetadata()) {
                    storeMetadata.put(refTable.getId(), refTable);
                    indexStoreMetadata(refTable);
                }
            }
        }
    }


    @Override
    public void deactivatePlayer(Client client) {
        EntityRef character = client.getEntity().getComponent(ClientComponent.class).character;
        EntityData.PlayerStore playerStore = createPlayerStore(client, character, true);
        unloadedAndUnsavedPlayerMap.put(client.getId(), playerStore);
    }

    private EntityData.PlayerStore loadPlayerStoreData(String playerId) {
        EntityData.PlayerStore  disposedUnsavedPlayer = unloadedAndUnsavedPlayerMap.get(playerId);
        if (disposedUnsavedPlayer != null) {
            return disposedUnsavedPlayer;
        }
        EntityData.PlayerStore  disposedSavingPlayer = unloadedAndSavingPlayerMap.get(playerId);
        if (disposedSavingPlayer != null) {
            return disposedSavingPlayer;
        }
        Path storePath = storagePathProvider.getPlayerFilePath(playerId);
        if (Files.isRegularFile(storePath)) {
            try (InputStream inputStream = new BufferedInputStream(Files.newInputStream(storePath))) {
                return EntityData.PlayerStore.parseFrom(inputStream);
            } catch (IOException e) {
                logger.error("Failed to load player data for {}", playerId, e);
            }
        }
        return null;
    }

    @Override
    public PlayerStore loadPlayerStore(String playerId) {
        EntityData.PlayerStore store = loadPlayerStoreData(playerId);
        if (store != null) {
            TIntSet validRefs = null;
            StoreMetadata table = storeMetadata.get(new PlayerStoreId(playerId));
            if (table != null) {
                validRefs = table.getExternalReferences();
            }
            return new PlayerStoreInternal(playerId, store, validRefs, this, entityManager);
        }
        return new PlayerStoreInternal(playerId, this, entityManager);
    }

    /**
     *
     * @param unsavedEntities currently loaded persistent entities without owner that have not been saved yet.
     *                        This method removes entities it saves.
     */
    private void addChunksToSaveTransaction(SaveTransactionBuilder saveTransactionBuilder, ChunkProvider chunkProvider,
                                            Set<EntityRef> unsavedEntities) {
        unloadedAndSavingChunkMap.clear();
        /**
         * New entries might be added concurrently. By using putAll + clear to transfer entries we might loose new
         * ones added in between putAll and clear. Bz iterating we can make sure that all entires removed
         * from unloadedAndUnsavedChunkMap get added to unloadedAndSavingChunkMap.
         */
        Iterator<Map.Entry<Vector3i, CompressedChunkBuilder>> unsavedEntryIterator = unloadedAndUnsavedChunkMap.entrySet().iterator();
        while(unsavedEntryIterator.hasNext()) {
            Map.Entry<Vector3i, CompressedChunkBuilder> entry = unsavedEntryIterator.next();
            unloadedAndSavingChunkMap.put(entry.getKey(), entry.getValue());
            unsavedEntryIterator.remove();
        }

        Map<Vector3i, Collection<EntityRef>> chunkPosToEntitiesMap = createChunkPosToUnsavedEntitiesMap();

        for (Chunk chunk : chunkProvider.getAllChunks()) {
            if (chunk.isReady()) {
                // If there is a newer undisposed version of the chunk,we don't need to save the disposed version:
                unloadedAndSavingChunkMap.remove(chunk.getPosition());
                Collection<EntityRef> entitiesToStore = chunkPosToEntitiesMap.get(chunk.getPosition());
                if (entitiesToStore == null) {
                    entitiesToStore = Collections.EMPTY_SET;
                }
                unsavedEntities.removeAll(entitiesToStore);
                CompressedChunkBuilder compressedChunkBuilder = createCompressedChunkBuilder(chunk,
                        entitiesToStore, false);
                saveTransactionBuilder.addCompressedChunkBuilder(chunk.getPosition(), compressedChunkBuilder);
            }
        }

        for (Map.Entry<Vector3i, CompressedChunkBuilder> entry: unloadedAndSavingChunkMap.entrySet()) {
            saveTransactionBuilder.addCompressedChunkBuilder(entry.getKey(), entry.getValue());
        }
    }

    private Map<Vector3i, Collection<EntityRef>> createChunkPosToUnsavedEntitiesMap() {
        Map<Vector3i, Collection<EntityRef>> chunkPosToEntitiesMap = Maps.newHashMap();
        for (EntityRef entity : entityManager.getEntitiesWith(LocationComponent.class)) {
            /*
             * Note: Entities with owners get saved with the owner. Entities that are always relevant don't get stored
             * in chunk as the chunk is not always loaded
             */
            if (entity.isPersistent() && !entity.getOwner().exists() && !entity.hasComponent(ClientComponent.class)
                    && !entity.isAlwaysRelevant()) {
                LocationComponent locationComponent = entity.getComponent(LocationComponent.class);
                if (locationComponent != null) {
                    Vector3f loc = locationComponent.getWorldPosition();
                    Vector3i chunkPos = TeraMath.calcChunkPos((int) loc.x, (int) loc.y, (int) loc.z);
                    Collection<EntityRef> collection = chunkPosToEntitiesMap.get(chunkPos);
                    if (collection == null) {
                        collection = Lists.newArrayList();
                        chunkPosToEntitiesMap.put(chunkPos, collection);
                    }
                    collection.add(entity);
                }
            }
        }
        return chunkPosToEntitiesMap;
    }

    /**
     *This method should only be called by the main thread.
     *
     * @param entitiesToSave all persistent entities within the given chunk
     * @param deactivate if true the entities of the chunk will be deaktivated and the chunk data will be used directly.
     *                 If deactivate is false then the entities won't be touched and a chunk will be but in
     *                 snapshot mode so that concurrent modifcations (and possibly future unload) is possible.
     */
    private CompressedChunkBuilder createCompressedChunkBuilder(Chunk chunk,
                                                                Collection<EntityRef> entitiesToSave,
                                                                boolean deactivate) {
        EntityStorer storer = new EntityStorer(entityManager);
        for (EntityRef entityRef : entitiesToSave) {
            if (entityRef.isPersistent()) {
                storer.store(entityRef, false);
            }
        }
        EntityData.EntityStore entityStore = storer.finaliseStore();
        TIntSet externalRefs = storer.getExternalReferences();

        Vector3i chunkPosition = chunk.getPosition();

        ChunkImpl chunkImpl = (ChunkImpl) chunk;
        boolean viaSnapshot = !deactivate;
        if (viaSnapshot) {
            chunkImpl.createSnapshot();
        }
        CompressedChunkBuilder compressedChunkBuilder = new CompressedChunkBuilder(entityStore, chunkImpl, viaSnapshot);

        if (externalRefs.size() > 0) {
            StoreMetadata metadata = new StoreMetadata(new ChunkStoreId(chunkPosition), externalRefs);
            indexStoreMetadata(metadata);
        }

        return compressedChunkBuilder;


    }



    @Override
    public void requestSaving() {
        this.saveRequested = true;
    }

    @Override
    public void waitForCompletionOfPreviousSaveAndStartSaving() {
        waitForCompletionOfPreviousSave();
        startSaving();
    }

    private void waitForCompletionOfPreviousSave() {
        if (saveTransaction != null && saveTransaction.getResult() == null) {
            saveThreadManager.shutdown(new ShutdownTask(), true);
            saveThreadManager.restart();
        }
        checkSaveTransactionAndClearUpIfItIsDone();
    }

    private SaveTransaction createSaveTransaction() {
        SaveTransactionBuilder saveTransactionBuilder = new SaveTransactionBuilder(storeChunksInZips,
                storagePathProvider);

        /**
         * Currently loaded persistent entities without owner that have not been saved yet.
         */
        Set<EntityRef> unsavedEntities = new HashSet<>();
        for (EntityRef entity: entityManager.getAllEntities()) {
            if (entity.isPersistent() && !entity.getOwner().exists()) {
                unsavedEntities.add(entity);
            }
        }
        ChunkProvider chunkProvider = CoreRegistry.get(ChunkProvider.class);
        NetworkSystem networkSystem = CoreRegistry.get(NetworkSystem.class);

        addChunksToSaveTransaction(saveTransactionBuilder, chunkProvider, unsavedEntities);
        addPlayersToSaveTransaction(saveTransactionBuilder, networkSystem, unsavedEntities);
        addGlobalStoreToSaveTransaction(saveTransactionBuilder, unsavedEntities);
        addGameManifestToSaveTransaction(saveTransactionBuilder);

        return saveTransactionBuilder.build();
    }


    /**
     *
     * @param unsavedEntities currently loaded persistent entities without owner that have not been saved yet.
     *                        This method removes entities it saves.
     */
    private void addPlayersToSaveTransaction(SaveTransactionBuilder saveTransactionBuilder, NetworkSystem networkSystem,
                                             Set<EntityRef> unsavedEntities) {
        unloadedAndSavingPlayerMap.clear();
        /**
         * New entries might be added concurrently. By using putAll + clear to transfer entries we might loose new
         * ones added in between putAll and clear. By iterating we can make sure that all entities removed
         * from unloadedAndUnsavedPlayerMap get added to unloadedAndSavingPlayerMap.
         */
        Iterator<Map.Entry<String, EntityData.PlayerStore>> unsavedEntryIterator = unloadedAndUnsavedPlayerMap.entrySet().iterator();
        while(unsavedEntryIterator.hasNext()) {
            Map.Entry<String, EntityData.PlayerStore> entry = unsavedEntryIterator.next();
            unloadedAndSavingPlayerMap.put(entry.getKey(), entry.getValue());
            unsavedEntryIterator.remove();
        }

        for (Client client : networkSystem.getPlayers()) {
            // If there is a newer undisposed version of the player,we don't need to save the disposed version:
            unloadedAndSavingPlayerMap.remove(client.getId());
            EntityRef character = client.getEntity().getComponent(ClientComponent.class).character;
            unsavedEntities.remove(character);
            saveTransactionBuilder.addPlayerStore(client.getId(), createPlayerStore(client, character, false));
        }

        for (Map.Entry<String, EntityData.PlayerStore> entry: unloadedAndSavingPlayerMap.entrySet()) {
            saveTransactionBuilder.addPlayerStore(entry.getKey(), entry.getValue());
        }
    }

    private EntityData.PlayerStore createPlayerStore(Client client, EntityRef character, boolean deactivate) {
        String playerId = client.getId();
        PlayerStore playerStore = new PlayerStoreInternal(playerId, this, entityManager);
        if (character.exists()) {
            playerStore.setCharacter(character);
        }

        boolean hasCharacter = character.exists();
        LocationComponent location = character.getComponent(LocationComponent.class);
        Vector3f relevanceLocation;
        if (location != null) {
            relevanceLocation = location.getWorldPosition();
        } else {
            relevanceLocation = new Vector3f();
        }

        EntityData.PlayerStore.Builder playerEntityStore = EntityData.PlayerStore.newBuilder();
        playerEntityStore.setCharacterPosX(relevanceLocation.x);
        playerEntityStore.setCharacterPosY(relevanceLocation.y);
        playerEntityStore.setCharacterPosZ(relevanceLocation.z);
        playerEntityStore.setHasCharacter(hasCharacter);
        EntityStorer storer = new EntityStorer(entityManager);
        storer.store(character, PlayerStoreInternal.CHARACTER, deactivate);
        playerEntityStore.setStore(storer.finaliseStore());


        TIntSet externalReference = storer.getExternalReferences();
        if (externalReference.size() > 0) {
            StoreMetadata metadata = new StoreMetadata(new PlayerStoreId(playerId), externalReference);
             indexStoreMetadata(metadata);
        }
        return playerEntityStore.build();
    }


    private Collection<EntityRef> getEntitiesOfChunk(Chunk chunk) {
        List<EntityRef> entitiesToStore = Lists.newArrayList();

        AABB aabb = chunk.getAABB();
        for (EntityRef entity : entityManager.getEntitiesWith(LocationComponent.class)) {
            if (!entity.getOwner().exists() && !entity.isAlwaysRelevant() && !entity.hasComponent(ClientComponent.class)) {
                LocationComponent loc = entity.getComponent(LocationComponent.class);
                if (loc != null) {
                    if (aabb.contains(loc.getWorldPosition())) {
                        entitiesToStore.add(entity);
                    }
                }
            }
        }
        return entitiesToStore;
    }

    @Override
    public void deactivateChunk(Chunk chunk) {
        Collection<EntityRef> entitiesOfChunk = getEntitiesOfChunk(chunk);
        unloadedAndUnsavedChunkMap.put(chunk.getPosition(), createCompressedChunkBuilder(chunk, entitiesOfChunk, true));
    }


    private byte[] loadCompressedChunk(Vector3i chunkPos) {
        CompressedChunkBuilder disposedUnsavedChunk = unloadedAndUnsavedChunkMap.get(chunkPos);
        if (disposedUnsavedChunk != null) {
            return disposedUnsavedChunk.buildEncodedChunk();
        }
        CompressedChunkBuilder disposedSavingChunk = unloadedAndSavingChunkMap.get(chunkPos);
        if (disposedSavingChunk != null) {
            return disposedSavingChunk.buildEncodedChunk();
        }

        if (storeChunksInZips) {
            return loadChunkZip(chunkPos);
        } else {
            Path chunkPath = storagePathProvider.getChunkPath(chunkPos);
            if (Files.isRegularFile(chunkPath)) {
                try {
                    return Files.readAllBytes(chunkPath);
                } catch (IOException e) {
                    logger.error("Failed to load chunk {}", chunkPos, e);
                }
            }
        }
        return null;
    }

    @Override
    public ChunkStore loadChunkStore(Vector3i chunkPos) {
        byte[] chunkData = loadCompressedChunk(chunkPos);
        ChunkStore store = null;
        if (chunkData != null) {
            TIntSet validRefs = null;
            StoreMetadata table = storeMetadata.get(new ChunkStoreId(chunkPos));
            if (table != null) {
                validRefs = table.getExternalReferences();
            }
            ByteArrayInputStream bais = new ByteArrayInputStream(chunkData);
            try (GZIPInputStream gzipIn = new GZIPInputStream(bais)) {
                EntityData.ChunkStore storeData = EntityData.ChunkStore.parseFrom(gzipIn);
                store = new ChunkStoreInternal(storeData, validRefs, this, entityManager);
            } catch (IOException e) {
                logger.error("Failed to read existing saved chunk {}", chunkPos);
            }
        }
        return store;
    }

    private byte[] loadChunkZip(Vector3i chunkPos) {
        byte[] chunkData = null;
        Vector3i chunkZipPos = storagePathProvider.getChunkZipPosition(chunkPos);
        Path chunkPath = storagePathProvider.getChunkZipPath(chunkZipPos);
        if (Files.isRegularFile(chunkPath)) {
            try (FileSystem chunkZip = FileSystems.newFileSystem(chunkPath, null)) {
                Path targetChunk = chunkZip.getPath(storagePathProvider.getChunkFilename(chunkPos));
                if (Files.isRegularFile(targetChunk)) {
                    chunkData = Files.readAllBytes(targetChunk);
                }
            } catch (IOException e) {
                logger.error("Failed to load chunk zip {}", chunkPath, e);
            }
        }
        return chunkData;
    }


    private void indexStoreMetadata(StoreMetadata metadata) {
        storeMetadata.put(metadata.getId(), metadata);
        TIntIterator iterator = metadata.getExternalReferences().iterator();
        while (iterator.hasNext()) {
            int refId = iterator.next();
            List<StoreMetadata> tables = externalRefHolderLookup.get(refId);
            if (tables == null) {
                tables = Lists.newArrayList();
                externalRefHolderLookup.put(refId, tables);
            }
            tables.add(metadata);
        }
    }

    @Override
    public void onEntityDestroyed(int entityId) {
        List<StoreMetadata> tables = externalRefHolderLookup.remove(entityId);
        if (tables != null) {
            for (StoreMetadata table : tables) {
                table.getExternalReferences().remove(entityId);
                if (table.getExternalReferences().isEmpty()) {
                    storeMetadata.remove(table.getId());
                }
            }
        }
    }

    private void addGameManifestToSaveTransaction(SaveTransactionBuilder saveTransactionBuilder) {
        BlockManager blockManager = CoreRegistry.get(BlockManager.class);
        BiomeManager biomeManager = CoreRegistry.get(BiomeManager.class);
        WorldProvider worldProvider = CoreRegistry.get(WorldProvider.class);
        Game game = CoreRegistry.get(Game.class);

        GameManifest gameManifest = new GameManifest(game.getName(), game.getSeed(), game.getTime().getGameTimeInMs());
        for (Module module : CoreRegistry.get(ModuleManager.class).getEnvironment()) {
            gameManifest.addModule(module.getId(), module.getVersion());
        }

        List<String> registeredBlockFamilies = Lists.newArrayList();
        for (BlockFamily family : blockManager.listRegisteredBlockFamilies()) {
            registeredBlockFamilies.add(family.getURI().toString());
        }
        gameManifest.setRegisteredBlockFamilies(registeredBlockFamilies);
        gameManifest.setBlockIdMap(blockManager.getBlockIdMap());
        List<Biome> biomes = biomeManager.getBiomes();
        Map<String, Short> biomeIdMap = new HashMap<>(biomes.size());
        for (Biome biome : biomes) {
            short shortId = biomeManager.getBiomeShortId(biome);
            String id = biomeManager.getBiomeId(biome);
            biomeIdMap.put(id, shortId);
        }
        gameManifest.setBiomeIdMap(biomeIdMap);
        gameManifest.addWorld(worldProvider.getWorldInfo());
        saveTransactionBuilder.setGameManifest(gameManifest);
    }

    @Override
    public void update() {
        if (!isRunModeAllowSaving()) {
            return;
        }
        if (isSaving()) {
            return;
        }

        checkSaveTransactionAndClearUpIfItIsDone();
        if (saveRequested || isSavingNecessary()) {
            startSaving();
        }
    }

    private boolean isRunModeAllowSaving() {
        NetworkSystem networkSystem = CoreRegistry.get(NetworkSystem.class);
        return networkSystem.getMode().isAuthority();
    }

    private void startSaving() {
        logger.info("Saving - Creating game snapshot");
        PerformanceMonitor.startActivity("Auto Saving");
        ComponentSystemManager componentSystemManager = CoreRegistry.get(ComponentSystemManager.class);
        for (ComponentSystem sys : componentSystemManager.iterateAll()) {
            sys.preSave();
        }

        saveRequested = false;
        saveTransaction = createSaveTransaction();
        saveThreadManager.offer(saveTransaction);

        for (ComponentSystem sys : componentSystemManager.iterateAll()) {
            sys.postSave();
        }
        scheduleNextAutoSave();
        PerformanceMonitor.endActivity();
        logger.info("Saving - Snapshot created: Writing phase starts");
    }


    private boolean isSavingNecessary() {
        ChunkProvider chunkProvider = CoreRegistry.get(ChunkProvider.class);
        int unloadedChunkCount = unloadedAndUnsavedChunkMap.size();
        int loadedChunkCount = chunkProvider.getAllChunks().size();
        double totalChunkCount = unloadedChunkCount + loadedChunkCount;
        double percentageUnloaded = 100.0 * (unloadedChunkCount / (double) totalChunkCount);
        if (percentageUnloaded >= config.getSystem().getMaxUnloadedChunksPercentageTillSave()) {
            return true;
        }

        long currentTime = System.currentTimeMillis();
        if (nextAutoSave == null) {
            scheduleNextAutoSave();
            return false;
        }
        if (currentTime >= nextAutoSave) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * For tests only
     *
     */
    public void setStoreChunksInZips(boolean storeChunksInZips) {
        this.storeChunksInZips = storeChunksInZips;
    }

    private void scheduleNextAutoSave() {
        long msBetweenAutoSave = config.getSystem().getMaxSecondsBetweenSaves() * 1000;
        nextAutoSave = System.currentTimeMillis() + msBetweenAutoSave;
    }

    @Override
    public boolean isSaving() {
        return saveTransaction != null && saveTransaction.getResult() == null;
    }

    @Override
    public void checkAndRepairSaveIfNecessary() throws IOException {
        saveTransactionHelper.cleanupSaveTransactionDirectory();
        if (Files.exists(storagePathProvider.getUnmergedChangesPath())) {
            saveTransactionHelper.mergeChanges();
        }
    }


    @Override
    public void deleteWorld() {
        waitForCompletionOfPreviousSave();
        unloadedAndUnsavedChunkMap.clear();
        unloadedAndSavingChunkMap.clear();
        unloadedAndUnsavedPlayerMap.clear();
        unloadedAndSavingPlayerMap.clear();

        try {
            FilesUtil.recursiveDelete(storagePathProvider.getWorldPath());
        } catch (IOException e) {
            logger.error("Failed to purge chunks", e);
        }
    }

}
TOP

Related Classes of org.terasology.persistence.internal.StorageManagerInternal

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.