/* * Copyright 2014 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.generation; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ListMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.terasology.world.generator.plugin.WorldGeneratorPluginLibrary; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; /** */ public class WorldBuilder { private static final Logger logger = LoggerFactory.getLogger(WorldBuilder.class); private final List<FacetProvider> providersList = Lists.newArrayList(); private final Set<Class<? extends WorldFacet>> facetCalculationInProgress = Sets.newHashSet(); private final List<WorldRasterizer> rasterizers = Lists.newArrayList(); private final List<EntityProvider> entityProviders = new ArrayList<>(); private int seaLevel = 32; private Long seed; private WorldGeneratorPluginLibrary pluginLibrary; public WorldBuilder(WorldGeneratorPluginLibrary pluginLibrary) { this.pluginLibrary = pluginLibrary; } public WorldBuilder addProvider(FacetProvider provider) { providersList.add(provider); return this; } public WorldBuilder addRasterizer(WorldRasterizer rasterizer) { rasterizers.add(rasterizer); return this; } public WorldBuilder addEntities(EntityProvider entityProvider) { entityProviders.add(entityProvider); return this; } public WorldBuilder addPlugins() { pluginLibrary.instantiateAllOfType(FacetProviderPlugin.class).forEach(this::addProvider); pluginLibrary.instantiateAllOfType(WorldRasterizerPlugin.class).forEach(this::addRasterizer); pluginLibrary.instantiateAllOfType(EntityProviderPlugin.class).forEach(this::addEntities); return this; } /** * @param level the sea level, measured in blocks * @return this */ public WorldBuilder setSeaLevel(int level) { this.seaLevel = level; return this; } public void setSeed(long seed) { this.seed = seed; } public World build() { // TODO: ensure the required providers are present if (seed == null) { throw new IllegalStateException("Seed has not been set"); } for (FacetProvider provider : providersList) { provider.setSeed(seed); } ListMultimap<Class<? extends WorldFacet>, FacetProvider> providerChains = determineProviderChains(); return new WorldImpl(providerChains, rasterizers, entityProviders, determineBorders(providerChains), seaLevel); } private Map<Class<? extends WorldFacet>, Border3D> determineBorders(ListMultimap<Class<? extends WorldFacet>, FacetProvider> providerChains) { Map<Class<? extends WorldFacet>, Border3D> borders = Maps.newHashMap(); for (Class<? extends WorldFacet> facet : providerChains.keySet()) { ensureBorderCalculatedForFacet(facet, providerChains, borders); } return borders; } private void ensureBorderCalculatedForFacet(Class<? extends WorldFacet> facet, ListMultimap<Class<? extends WorldFacet>, FacetProvider> providerChains, Map<Class<? extends WorldFacet>, Border3D> borders) { if (!borders.containsKey(facet)) { Border3D border = new Border3D(0, 0, 0); int maxSide = 0; int maxTop = 0; int maxBottom = 0; for (FacetProvider facetProvider : providerChains.values()) { // Find all facets that require it Requires requires = facetProvider.getClass().getAnnotation(Requires.class); Produces produces = facetProvider.getClass().getAnnotation(Produces.class); Updates updates = facetProvider.getClass().getAnnotation(Updates.class); if (requires != null) { for (Facet requiredFacet : requires.value()) { if (requiredFacet.value() == facet) { FacetBorder requiredBorder = requiredFacet.border(); if (produces != null) { for (Class<? extends WorldFacet> producedFacet : produces.value()) { ensureBorderCalculatedForFacet(producedFacet, providerChains, borders); Border3D borderForProducedFacet = borders.get(producedFacet); border = border.maxWith( borderForProducedFacet.getTop() + requiredBorder.top(), borderForProducedFacet.getBottom() + requiredBorder.bottom(), borderForProducedFacet.getSides() + requiredBorder.sides()); } } if (updates != null) { for (Facet producedFacetAnnotation : updates.value()) { Class<? extends WorldFacet> producedFacet = producedFacetAnnotation.value(); FacetBorder borderForFacetAnnotation = producedFacetAnnotation.border(); ensureBorderCalculatedForFacet(producedFacet, providerChains, borders); Border3D borderForProducedFacet = borders.get(producedFacet); border = border.maxWith( borderForProducedFacet.getTop() + requiredBorder.top() + borderForFacetAnnotation.top(), borderForProducedFacet.getBottom() + requiredBorder.bottom() + borderForFacetAnnotation.bottom(), borderForProducedFacet.getSides() + requiredBorder.sides() + borderForFacetAnnotation.sides()); } } } } } //Get biggest border for facet?! Create an array of borders and search for maximum. // Check if there are update annotation for facet, if there are search for biggest border requested from providers and replace value if (updates != null) { for (Facet producedFacetAnnotation : updates.value()) { if (producedFacetAnnotation.value() == facet) { FacetBorder borderForFacetAnnotation = producedFacetAnnotation.border(); if (maxSide < borderForFacetAnnotation.sides()) { maxSide = borderForFacetAnnotation.sides(); } if (maxTop < borderForFacetAnnotation.top()) { maxTop = borderForFacetAnnotation.top(); } if (maxBottom < borderForFacetAnnotation.bottom()) { maxBottom = borderForFacetAnnotation.bottom(); } } } border = border.maxWith(maxTop, maxBottom, maxSide); } } borders.put(facet, border); } } private ListMultimap<Class<? extends WorldFacet>, FacetProvider> determineProviderChains() { ListMultimap<Class<? extends WorldFacet>, FacetProvider> result = ArrayListMultimap.create(); Set<Class<? extends WorldFacet>> facets = new LinkedHashSet<>(); for (FacetProvider provider : providersList) { Produces produces = provider.getClass().getAnnotation(Produces.class); if (produces != null) { facets.addAll(Arrays.asList(produces.value())); } Updates updates = provider.getClass().getAnnotation(Updates.class); if (updates != null) { for (Facet facet : updates.value()) { facets.add(facet.value()); } } } for (Class<? extends WorldFacet> facet : facets) { determineProviderChainFor(facet, result); if (logger.isDebugEnabled()) { StringBuilder text = new StringBuilder(facet.getSimpleName()); text.append(" --> "); Iterator<FacetProvider> it = result.get(facet).iterator(); while (it.hasNext()) { text.append(it.next().getClass().getSimpleName()); if (it.hasNext()) { text.append(", "); } } logger.debug(text.toString()); } } return result; } private void determineProviderChainFor(Class<? extends WorldFacet> facet, ListMultimap<Class<? extends WorldFacet>, FacetProvider> result) { if (result.containsKey(facet)) { return; } if (!facetCalculationInProgress.add(facet)) { throw new RuntimeException("Circular dependency detected when calculating facet provider ordering for " + facet); } Set<FacetProvider> orderedProviders = Sets.newLinkedHashSet(); // first add all @Produces facet providers FacetProvider producer = null; for (FacetProvider provider : providersList) { if (producesFacet(provider, facet)) { if (producer != null) { logger.warn("Facet already produced by {} and overwritten by {}", producer, provider); } // add all required facets for producing provider for (Facet requirement : requiredFacets(provider)) { determineProviderChainFor(requirement.value(), result); orderedProviders.addAll(result.get(requirement.value())); } // add all updated facets for producing provider for (Facet updated : updatedFacets(provider)) { determineProviderChainFor(updated.value(), result); orderedProviders.addAll(result.get(updated.value())); } orderedProviders.add(provider); producer = provider; } } if (producer == null) { logger.warn("No facet provider found that produces {}", facet); } // then add all @Updates facet providers providersList.stream().filter(provider -> updatesFacet(provider, facet)).forEach(provider -> { // add all required facets for updating provider for (Facet requirement : requiredFacets(provider)) { determineProviderChainFor(requirement.value(), result); orderedProviders.addAll(result.get(requirement.value())); } // the provider updates this and other facets // just add producers for the other facets for (Facet updated : updatedFacets(provider)) { for (FacetProvider fp : providersList) { // only add @Produces providers to avoid infinite recursion if (producesFacet(fp, updated.value())) { orderedProviders.add(fp); } } } orderedProviders.add(provider); }); result.putAll(facet, orderedProviders); facetCalculationInProgress.remove(facet); } private Facet[] requiredFacets(FacetProvider provider) { Requires requirements = provider.getClass().getAnnotation(Requires.class); if (requirements != null) { return requirements.value(); } return new Facet[0]; } private Facet[] updatedFacets(FacetProvider provider) { Updates updates = provider.getClass().getAnnotation(Updates.class); if (updates != null) { return updates.value(); } return new Facet[0]; } private boolean producesFacet(FacetProvider provider, Class<? extends WorldFacet> facet) { Produces produces = provider.getClass().getAnnotation(Produces.class); if (produces != null && Arrays.asList(produces.value()).contains(facet)) { return true; } return false; } private boolean updatesFacet(FacetProvider provider, Class<? extends WorldFacet> facet) { Updates updates = provider.getClass().getAnnotation(Updates.class); if (updates != null) { for (Facet updatedFacet : updates.value()) { if (updatedFacet.value() == facet) { return true; } } } return false; } public FacetedWorldConfigurator createConfigurator() { List<ConfigurableFacetProvider> configurables = new ArrayList<>(); for (FacetProvider facetProvider : providersList) { if (facetProvider instanceof ConfigurableFacetProvider) { configurables.add((ConfigurableFacetProvider) facetProvider); } } FacetedWorldConfigurator worldConfigurator = new FacetedWorldConfigurator(configurables); return worldConfigurator; } }