/* * Copyright 2016 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.world.block.internal; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import gnu.trove.iterator.TObjectShortIterator; import gnu.trove.map.TObjectShortMap; import gnu.trove.map.TShortObjectMap; import gnu.trove.map.hash.TObjectShortHashMap; import gnu.trove.map.hash.TShortObjectHashMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.terasology.assets.ResourceUrn; import org.terasology.assets.management.AssetManager; import org.terasology.world.block.Block; import org.terasology.world.block.BlockManager; import org.terasology.world.block.BlockUri; import org.terasology.world.block.BlockUriParseException; import org.terasology.world.block.family.BlockFamily; import org.terasology.world.block.loader.BlockFamilyDefinition; import org.terasology.world.block.shapes.BlockShape; import org.terasology.world.block.tiles.WorldAtlas; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.ReentrantLock; public class BlockManagerImpl extends BlockManager { private static final Logger logger = LoggerFactory.getLogger(BlockManagerImpl.class); // This is the id we assign to blocks whose mappings are missing. This shouldn't happen, but in case it does // we set them to the last id (don't want to use 0 as they would override air) private static final short UNKNOWN_ID = (short) 65535; private static final int MAX_ID = 65534; private static final ResourceUrn CUBE_SHAPE_URN = new ResourceUrn("engine:cube"); private AssetManager assetManager; private BlockBuilder blockBuilder; private ReentrantLock lock = new ReentrantLock(); private AtomicReference<RegisteredState> registeredBlockInfo = new AtomicReference<>(new RegisteredState()); private Set<BlockRegistrationListener> listeners = Sets.newLinkedHashSet(); private boolean generateNewIds; private int nextId = 1; // Cache this for performance reasons because a lookup by BlockURI happens the first time a block is set when getting the previous block. // This causes performance problems eventually down the line when it then uses the ResourceUrn's hashcode to do a lookup into the block map. private Block airBlock; public BlockManagerImpl(WorldAtlas atlas, AssetManager assetManager) { this(atlas, assetManager, true); } public BlockManagerImpl(WorldAtlas atlas, AssetManager assetManager, boolean generateNewIds) { this.generateNewIds = generateNewIds; this.assetManager = assetManager; this.blockBuilder = new BlockBuilder(atlas); } public void initialise(List<String> registeredBlockFamilies, Map<String, Short> knownBlockMappings) { if (knownBlockMappings.size() >= MAX_ID) { nextId = UNKNOWN_ID; } else if (knownBlockMappings.size() > 0) { nextId = (short) knownBlockMappings.size(); } registeredBlockInfo.set(new RegisteredState()); for (String rawFamilyUri : registeredBlockFamilies) { try { BlockUri familyUri = new BlockUri(rawFamilyUri); Optional<BlockFamily> family = loadFamily(familyUri); if (family.isPresent()) { for (Block block : family.get().getBlocks()) { Short id = knownBlockMappings.get(block.getURI().toString()); if (id != null) { block.setId(id); } else { logger.error("Missing id for block {} in provided family {}", block.getURI(), family.get().getURI()); if (generateNewIds) { block.setId(getNextId()); } else { block.setId(UNKNOWN_ID); } } } registerFamily(family.get()); } } catch (BlockUriParseException e) { logger.error("Failed to parse block family, skipping", e); } } } public void dispose() { } private short getNextId() { if (nextId > MAX_ID) { return UNKNOWN_ID; } return (short) nextId++; } private Block getAirBlock() { if (airBlock == null) { airBlock = getBlock(AIR_ID); } return airBlock; } public void subscribe(BlockRegistrationListener listener) { this.listeners.add(listener); } public void unsubscribe(BlockRegistrationListener listener) { this.listeners.remove(listener); } public void receiveFamilyRegistration(BlockUri familyUri, Map<String, Integer> registration) { Optional<BlockFamily> family = loadFamily(familyUri); if (family.isPresent()) { lock.lock(); try { for (Block block : family.get().getBlocks()) { Integer id = registration.get(block.getURI().toString()); if (id != null) { block.setId((short) id.intValue()); } else { logger.error("Missing id for block {} in registered family {}", block.getURI(), familyUri); block.setId(UNKNOWN_ID); } } registerFamily(family.get()); } finally { lock.unlock(); } } } @VisibleForTesting protected void registerFamily(BlockFamily family) { Preconditions.checkNotNull(family); logger.info("Registered {}", family); lock.lock(); try { RegisteredState newState = new RegisteredState(registeredBlockInfo.get()); newState.registeredFamilyByUri.put(family.getURI(), family); for (Block block : family.getBlocks()) { registerBlock(block, newState); } registeredBlockInfo.set(newState); } finally { lock.unlock(); } for (BlockRegistrationListener listener : listeners) { listener.onBlockFamilyRegistered(family); } } private void registerBlock(Block block, RegisteredState newState) { if (block.getId() != UNKNOWN_ID) { logger.info("Registered Block {} with id {}", block, block.getId()); newState.blocksById.put(block.getId(), block); newState.idByUri.put(block.getURI(), block.getId()); } else { logger.info("Failed to register block {} - no id", block, block.getId()); } newState.blocksByUri.put(block.getURI(), block); } @Override public Map<String, Short> getBlockIdMap() { Map<String, Short> result = Maps.newHashMapWithExpectedSize(registeredBlockInfo.get().idByUri.size()); TObjectShortIterator<BlockUri> iterator = registeredBlockInfo.get().idByUri.iterator(); while (iterator.hasNext()) { iterator.advance(); result.put(iterator.key().toString(), iterator.value()); } return result; } @Override public BlockFamily getBlockFamily(String uri) { if (!uri.contains(":")) { Set<ResourceUrn> resourceUrns = assetManager.resolve(uri, BlockFamilyDefinition.class); if (resourceUrns.size() == 1) { return getBlockFamily(new BlockUri(resourceUrns.iterator().next())); } else { if (resourceUrns.size() > 0) { logger.error("Failed to resolve block family '{}', too many options - {}", uri, resourceUrns); } else { logger.error("Failed to resolve block family '{}'", uri); } } } else { try { BlockUri blockUri = new BlockUri(uri); return getBlockFamily(blockUri); } catch (BlockUriParseException e) { logger.error("Failed to resolve block family '{}', invalid uri", uri); } } return getBlockFamily(AIR_ID); } @Override public BlockFamily getBlockFamily(BlockUri uri) { if (uri.getShapeUrn().isPresent() && uri.getShapeUrn().get().equals(CUBE_SHAPE_URN)) { return getBlockFamily(uri.getShapelessUri()); } BlockFamily family = registeredBlockInfo.get().registeredFamilyByUri.get(uri); if (family == null && generateNewIds) { Optional<BlockFamily> newFamily = loadFamily(uri); if (newFamily.isPresent()) { lock.lock(); try { for (Block block : newFamily.get().getBlocks()) { block.setId(getNextId()); } registerFamily(newFamily.get()); } finally { lock.unlock(); } return newFamily.get(); } } return family; } private Optional<BlockFamily> loadFamily(BlockUri uri) { Optional<BlockFamilyDefinition> familyDef = assetManager.getAsset(uri.getBlockFamilyDefinitionUrn(), BlockFamilyDefinition.class); if (familyDef.isPresent() && familyDef.get().isLoadable()) { if (familyDef.get().isFreeform()) { ResourceUrn shapeUrn; if (uri.getShapeUrn().isPresent()) { shapeUrn = uri.getShapeUrn().get(); } else { shapeUrn = CUBE_SHAPE_URN; } Optional<BlockShape> shape = assetManager.getAsset(shapeUrn, BlockShape.class); if (shape.isPresent()) { return Optional.of(familyDef.get().createFamily(shape.get(), blockBuilder)); } } else if (!familyDef.get().isFreeform()) { return Optional.of(familyDef.get().createFamily(blockBuilder)); } } else { logger.error("Family not available: {}", uri); } return Optional.empty(); } @Override public Block getBlock(String uri) { try { return getBlock(new BlockUri(uri)); } catch (BlockUriParseException e) { logger.error("Attempt to fetch block with illegal uri '{}'", uri); return getAirBlock(); } } @Override public Block getBlock(BlockUri uri) { if (uri.getShapeUrn().isPresent() && uri.getShapeUrn().get().equals(CUBE_SHAPE_URN)) { return getBlock(uri.getShapelessUri()); } Block block = registeredBlockInfo.get().blocksByUri.get(uri); if (block == null) { // Check if partially registered by getting the block family BlockFamily family = getBlockFamily(uri.getFamilyUri()); if (family != null) { block = family.getBlockFor(uri); } if (block == null) { return getAirBlock(); } } return block; } @Override public Block getBlock(short id) { Block result = registeredBlockInfo.get().blocksById.get(id); if (result == null) { return getAirBlock(); } return result; } @Override public Collection<BlockUri> listRegisteredBlockUris() { return Collections.unmodifiableCollection(registeredBlockInfo.get().registeredFamilyByUri.keySet()); } @Override public Collection<BlockFamily> listRegisteredBlockFamilies() { return Collections.unmodifiableCollection(registeredBlockInfo.get().registeredFamilyByUri.values()); } @Override public int getBlockFamilyCount() { return registeredBlockInfo.get().registeredFamilyByUri.size(); } @Override public Collection<Block> listRegisteredBlocks() { return ImmutableList.copyOf(registeredBlockInfo.get().blocksById.valueCollection()); } private static class RegisteredState { private final Map<BlockUri, BlockFamily> registeredFamilyByUri; /* Blocks */ private final Map<BlockUri, Block> blocksByUri; private final TShortObjectMap<Block> blocksById; private final TObjectShortMap<BlockUri> idByUri; RegisteredState() { this.registeredFamilyByUri = Maps.newHashMap(); this.blocksByUri = Maps.newHashMap(); this.blocksById = new TShortObjectHashMap<>(); this.idByUri = new TObjectShortHashMap<>(); } RegisteredState(RegisteredState oldState) { this.registeredFamilyByUri = Maps.newHashMap(oldState.registeredFamilyByUri); this.blocksByUri = Maps.newHashMap(oldState.blocksByUri); this.blocksById = new TShortObjectHashMap<>(oldState.blocksById); this.idByUri = new TObjectShortHashMap<>(oldState.idByUri); } } }