Resource loading with mods has now been fixed. Resources are reloaded when changing the active mods. Added a debug option for reloading resources from disk (great for testing new ingame graphics).

This commit is contained in:
Stian Grenborgen 2022-04-23 13:51:23 +02:00
parent 368d8518d7
commit 752845c44f
12 changed files with 151 additions and 42 deletions

View File

@ -270,6 +270,7 @@ menuBar.view=View
menuBar.statusLine=Score: %score% | Gold: %gold% | Tax: %tax%% | Year: %year%
menuBar.debug=Debug
menuBar.debug.reloadResources=Reload resources
menuBar.debug.addBuilding=Add building to each colony
menuBar.debug.addFoundingFather=Add Founding Father
menuBar.debug.addGold=Add Gold

View File

@ -210,10 +210,10 @@ public final class FreeColClient {
// Not so easy, since the ActionManager also creates tile
// improvement actions, which depend on the specification.
// However, this step could probably be delayed.
ResourceManager.addMapping("base", baseData.getResourceMapping());
ResourceManager.setBaseData(baseData);
FreeColTcFile tcData = FreeColTcFile.getFreeColTcFile("classic");
ResourceManager.addMapping("tc", tcData.getResourceMapping());
ResourceManager.setTcData(tcData);
actionManager = new ActionManager(this);
actionManager.initializeActions(inGameController, connectController);
@ -224,10 +224,7 @@ public final class FreeColClient {
this.clientOptions.fixClientOptions();
// Reset the mod resources as a result of the client option update.
for (FreeColModFile f : this.clientOptions.getActiveMods()) {
ResourceManager.addMapping("mod " + f.getId(),
f.getResourceMapping());
}
ResourceManager.setMods(this.clientOptions.getActiveMods());
/*
* All mods are loaded, so the GUI can safely be created.
@ -884,7 +881,7 @@ public final class FreeColClient {
setFreeColServer(fcs);
setSinglePlayer(singlePlayer);
this.inGameController.setGameConnected();
ResourceManager.addMapping("game", fsg.getResourceMapping());
ResourceManager.setSavegameFile(fsg);
return fcs;
}

View File

@ -47,6 +47,7 @@ import net.sf.freecol.common.model.Player;
import net.sf.freecol.common.model.Specification;
import net.sf.freecol.common.model.StringTemplate;
import net.sf.freecol.common.model.Unit;
import net.sf.freecol.common.resources.ResourceManager;
import net.sf.freecol.common.util.Utils;
import net.sf.freecol.server.FreeColServer;
import net.sf.freecol.server.FreeColServer.ServerState;
@ -442,6 +443,11 @@ public final class ConnectController extends FreeColClientHolder {
port, serverName);
if (fcs == null) return false;
/*
* TODO: The choice of active mods with a specification should be stored in the savegame.
* These mods should also be loaded in the ResourceManager (after global mods without
* a specification).
*/
fcs.getGame().getSpecification().loadMods(options.getActiveMods());
fcc.setFreeColServer(fcs);

View File

@ -2483,4 +2483,11 @@ public class GUI extends FreeColClientHolder {
* @return The panel shown.
*/
public FreeColPanel showWorkProductionPanel(Unit unit) { return null; }
/**
* Reloads all images managed by {@code ResourceManager}.
*/
public void reloadResources() {
}
}

View File

@ -50,6 +50,7 @@ import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.filechooser.FileFilter;
import net.sf.freecol.FreeCol;
@ -115,6 +116,7 @@ import net.sf.freecol.common.option.LanguageOption.Language;
import net.sf.freecol.common.option.Option;
import net.sf.freecol.common.option.OptionGroup;
import net.sf.freecol.common.resources.ImageCache;
import net.sf.freecol.common.resources.ResourceManager;
import net.sf.freecol.common.util.Introspector;
import net.sf.freecol.common.util.Utils;
@ -1357,6 +1359,22 @@ public class SwingGUI extends GUI {
refresh();
}
}
/**
* {@inheritDoc}
*/
@Override
public void reloadResources() {
ResourceManager.reload();
imageCache.clear();
refresh();
}
public void prepareResources() {
ResourceManager.prepare();
imageCache.clear();
refresh();
}
// Highest level panel and dialog handling
@ -1707,6 +1725,9 @@ public class SwingGUI extends GUI {
FreeColImageBorder.setScaleFactor(scaleFactor);
ResourceManager.setMods(getClientOptions().getActiveMods());
prepareResources();
final int fontSize = determineMainFontSizeUsingClientOptions(dpi);
FontLibrary.setMainFontSize(fontSize);

View File

@ -87,6 +87,15 @@ public class DebugMenu extends JMenu {
this.setOpaque(false);
this.setMnemonic(KeyEvent.VK_D);
add(this);
final JMenuItem reload = Utility.localizedMenuItem("menuBar.debug.reloadResources");
reload.setOpaque(false);
reload.addActionListener((event) -> {
freeColClient.getGUI().reloadResources();
});
this.add(reload);
addSeparator();
final JCheckBoxMenuItem sc
= Utility.localizedCheckBoxMenuItem("menuBar.debug.showCoordinates",

View File

@ -203,8 +203,9 @@ public class FreeColDataFile {
* data files.
*/
private List<String> handleResources(final Properties properties, ResourceMapping rc) {
List<String> virtualResources = new ArrayList<>();
Enumeration<?> pn = properties.propertyNames();
final ResourceFactory resourceFactory = new ResourceFactory();
final List<String> virtualResources = new ArrayList<>();
final Enumeration<?> pn = properties.propertyNames();
while (pn.hasMoreElements()) {
final String key = (String) pn.nextElement();
@ -219,18 +220,18 @@ public class FreeColDataFile {
if (value.startsWith(resourceScheme)) {
virtualResources.add(updatedKey);
} else {
handleNormalResource(rc, key, value);
handleNormalResource(resourceFactory, rc, key, value);
}
}
return virtualResources;
}
private void handleNormalResource(ResourceMapping rc, final String key, final String value) {
private void handleNormalResource(ResourceFactory resourceFactory, ResourceMapping rc, final String key, final String value) {
final URI uri = getURI(value);
if (uri == null) {
return;
}
final Resource resource = ResourceFactory.createResource(key, uri);
final Resource resource = resourceFactory.createResource(key, uri);
/*
* Rivers need new keys in order to support variations.
@ -239,7 +240,7 @@ public class FreeColDataFile {
if (resource instanceof ImageResource && supportsVariations) {
final ImageResource imageResource = (ImageResource) resource;
extendWithAdditionalSizesAndVariations(imageResource, value);
extendWithAdditionalSizesAndVariations(resourceFactory, imageResource, value);
}
if (resource != null) {
@ -309,7 +310,7 @@ public class FreeColDataFile {
return (key.endsWith(ending)) ? key.substring(0, key.length() - 3) : key;
}
private void extendWithAdditionalSizesAndVariations(ImageResource imageResource, String value) {
private void extendWithAdditionalSizesAndVariations(ResourceFactory resourceFactory, ImageResource imageResource, String value) {
Map<URI, List<URI>> variationsWithAlternateSizes = findVariationsWithAlternateSizes(value);
imageResource.addAlternativeResourceLocators(variationsWithAlternateSizes.get(null));
@ -317,7 +318,7 @@ public class FreeColDataFile {
.stream()
.filter(entry -> entry.getKey() != null)
.forEach(entry -> {
final ImageResource variationResource = (ImageResource) ResourceFactory.createResource(imageResource.getPrimaryKey(), entry.getKey());
final ImageResource variationResource = (ImageResource) resourceFactory.createResource(imageResource.getPrimaryKey(), entry.getKey());
if (variationResource != null) {
variationResource.addAlternativeResourceLocators(entry.getValue());
imageResource.addVariation(variationResource);

View File

@ -43,12 +43,14 @@ import java.util.logging.Logger;
import javax.imageio.ImageIO;
import net.sf.freecol.common.resources.Resource.Cleanable;
/**
* A {@code Resource} wrapping an {@code Image}.
* @see Resource
*/
public class ImageResource extends Resource {
public class ImageResource extends Resource implements Cleanable {
private static final Logger logger = Logger.getLogger(ImageResource.class.getName());
@ -295,6 +297,16 @@ public class ImageResource extends Resource {
return null;
}
@Override
public void clean() {
image = null;
if (loadedImages != null) {
loadedImages.clear();
}
if (variations != null) {
variations.stream().forEach(v -> v.clean());
}
}
private static boolean canUseBitmask(URI uri) {
/* TODO: Better method for determining images that can use a bitmask. */

View File

@ -35,19 +35,11 @@ public class ResourceFactory {
private static final Logger logger = Logger.getLogger(ResourceFactory.class.getName());
/**
* Takes a newly produced Resource.
*/
public interface ResourceSink {
void add(Resource r);
}
/**
* Ensures that only one {@code Resource} is created given the same {@code URI}.
*/
private static final Map<URI, Resource> resources = new HashMap<>();
private final Map<URI, Resource> resources = new HashMap<>();
/**
@ -58,7 +50,7 @@ public class ResourceFactory {
* instance.
* @return The <code>Resource</code> if created.
*/
public static Resource createResource(String primaryKey, URI uri) {
public Resource createResource(String primaryKey, URI uri) {
final Resource r = resources.get(uri);
if (r != null) {
return r;

View File

@ -27,13 +27,17 @@ import java.awt.Dimension;
import java.awt.Font;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import javax.swing.SwingUtilities;
import net.sf.freecol.FreeCol;
import net.sf.freecol.common.io.FreeColDataFile;
import net.sf.freecol.common.io.FreeColSavegameFile;
import net.sf.freecol.common.io.sza.SimpleZippedAnimation;
@ -53,23 +57,76 @@ public class ResourceManager {
/**
* All the mappings are merged in order into this single ResourceMapping.
*/
private static final ResourceMapping mergedContainer
= new ResourceMapping();
private static ResourceMapping mergedContainer = new ResourceMapping();
private static FreeColDataFile baseDataFile = null;
private static ResourceMapping baseResourceMapping = new ResourceMapping();
private static FreeColDataFile tcDataFile = null;
private static ResourceMapping tcResourceMapping = new ResourceMapping();
private static List<? extends FreeColDataFile> mods = new ArrayList<>();
private static List<ResourceMapping> modResourceMappings = new ArrayList<>();
private static FreeColSavegameFile savegameFile = null;
private static ResourceMapping savegameResourceMapping = new ResourceMapping();
public static void setBaseData(FreeColDataFile baseDataFile) {
ResourceManager.baseDataFile = baseDataFile;
baseResourceMapping = baseDataFile.getResourceMapping();
}
public static void setTcData(FreeColDataFile tcDataFile) {
ResourceManager.tcDataFile = tcDataFile;
tcResourceMapping = (tcDataFile != null) ? tcDataFile.getResourceMapping() : new ResourceMapping();
prepare();
}
public static <T extends FreeColDataFile> void setMods(List<T> mods) {
ResourceManager.mods = mods;
modResourceMappings = mods.stream().map(FreeColDataFile::getResourceMapping).collect(Collectors.toList());
prepare();
}
public static void setSavegameFile(FreeColSavegameFile savegameFile) {
ResourceManager.savegameFile = savegameFile;
savegameResourceMapping = (savegameFile != null) ? savegameFile.getResourceMapping() : new ResourceMapping();
prepare();
}
/**
* Sets the mappings specified in the date/base-directory.
* Do not access the mapping after the call.
*
* @param name The name of the mapping, for logging purposes.
* @param mapping The mapping between IDs and files.
*/
public static void addMapping(String name, ResourceMapping mapping) {
if (preloadThread != null) {
throw new IllegalStateException("New mappings should not be added while preloading is running.");
public static void prepare() {
finishPreloading();
final ResourceMapping newMergedContainer = new ResourceMapping();
newMergedContainer.addAll(baseResourceMapping);
newMergedContainer.addAll(tcResourceMapping);
for (ResourceMapping modResourceMapping : modResourceMappings) {
newMergedContainer.addAll(modResourceMapping);
}
newMergedContainer.addAll(savegameResourceMapping);
waitForPreloadingToStop();
mergedContainer = newMergedContainer;
}
/**
* Clear all caches and
*/
public static void reload() {
baseResourceMapping = baseDataFile.getResourceMapping();
tcResourceMapping = (tcDataFile != null) ? tcDataFile.getResourceMapping() : new ResourceMapping();
modResourceMappings = mods.stream().map(FreeColDataFile::getResourceMapping).collect(Collectors.toList());
savegameResourceMapping = (savegameFile != null) ? savegameFile.getResourceMapping() : new ResourceMapping();
prepare();
}
private static void waitForPreloadingToStop() {
try {
while (preloadThread != null) {
Thread.sleep(10);
}
} catch (InterruptedException e) {
logger.warning(e.getMessage());
}
logger.info("Resource manager adding mapping " + name);
mergedContainer.addAll(mapping);
}
/**

View File

@ -292,6 +292,10 @@ public final class ResourceMapping {
return ret;
}
public void clearCaches() {
imageResources.values().stream().forEach(r -> r.clean());
}
public interface PreloadController {
boolean shouldContinue();
}

View File

@ -56,7 +56,8 @@ public class SoundTest extends FreeColTestCase {
} catch (Exception e) {
fail("Could not load base data: " + e.getMessage());
}
ResourceManager.addMapping("testbase", baseData.getResourceMapping());
ResourceManager.setBaseData(baseData);
ResourceManager.prepare();
}
@Override
@ -86,7 +87,8 @@ public class SoundTest extends FreeColTestCase {
public void testClassic() {
FreeColTcFile tcData = FreeColTcFile.getFreeColTcFile("classic");
ResourceManager.addMapping("testtc", tcData.getResourceMapping());
ResourceManager.setTcData(tcData);
ResourceManager.prepare();
playSound("sound.intro.model.nation.english");
playSound("sound.intro.model.nation.dutch");