From 641f19dd6753f16b86427efd9ec99f0c49b6ca79 Mon Sep 17 00:00:00 2001 From: Speiger Date: Thu, 9 Jul 2026 13:54:58 +0200 Subject: [PATCH] Finished the reworking of the Asset Manager --- .settings/org.eclipse.buildship.core.prefs | 2 +- build.gradle | 1 + .../src/coreengine/assets/api/IAsset.java | 18 ++- .../coreengine/assets/api/IAssetPackage.java | 36 ++++- .../coreengine/assets/api/IAssetProvider.java | 56 ++++++- .../speiger/src/coreengine/assets/api/ID.java | 21 +-- .../coreengine/assets/api/IFileWatcher.java | 20 +++ .../assets/api/IResourceListener.java | 13 ++ .../coreengine/assets/api/ReloadContext.java | 11 ++ .../assets/impl/FolderAssetPackage.java | 77 +++++++--- .../coreengine/assets/impl/FolderWatcher.java | 110 ++++++++++++++ .../coreengine/assets/impl/ReloadManager.java | 119 +++++++++++++++ .../coreengine/assets/impl/SimpleAsset.java | 2 +- .../assets/impl/ZipAssetPackage.java | 129 +++++++++++++++++ .../assets/impl/parsers/Buffers.java | 33 +++++ .../assets/impl/parsers/Closables.java | 29 ++++ .../assets/impl/parsers/Serialized.java | 44 ++++++ .../assets/manager/AssetManager.java | 118 +++++++++++++++ .../assets/manager/ReloadBuilder.java | 56 +++++++ .../coreengine/assets/manager/ReloadTask.java | 72 +++++++++ .../assets/providers/BaseProvider.java | 10 ++ .../assets/providers/LayeredProvider.java | 137 ++++++++++++++++++ .../assets/providers/SingleProvider.java | 74 ++++++++++ .../assets/simple/SimpleAssetProvider.java | 38 ----- 24 files changed, 1147 insertions(+), 79 deletions(-) create mode 100644 src/assets/java/speiger/src/coreengine/assets/api/IFileWatcher.java create mode 100644 src/assets/java/speiger/src/coreengine/assets/api/IResourceListener.java create mode 100644 src/assets/java/speiger/src/coreengine/assets/api/ReloadContext.java create mode 100644 src/assets/java/speiger/src/coreengine/assets/impl/FolderWatcher.java create mode 100644 src/assets/java/speiger/src/coreengine/assets/impl/ReloadManager.java create mode 100644 src/assets/java/speiger/src/coreengine/assets/impl/ZipAssetPackage.java create mode 100644 src/assets/java/speiger/src/coreengine/assets/impl/parsers/Buffers.java create mode 100644 src/assets/java/speiger/src/coreengine/assets/impl/parsers/Closables.java create mode 100644 src/assets/java/speiger/src/coreengine/assets/impl/parsers/Serialized.java create mode 100644 src/assets/java/speiger/src/coreengine/assets/manager/AssetManager.java create mode 100644 src/assets/java/speiger/src/coreengine/assets/manager/ReloadBuilder.java create mode 100644 src/assets/java/speiger/src/coreengine/assets/manager/ReloadTask.java create mode 100644 src/assets/java/speiger/src/coreengine/assets/providers/BaseProvider.java create mode 100644 src/assets/java/speiger/src/coreengine/assets/providers/LayeredProvider.java create mode 100644 src/assets/java/speiger/src/coreengine/assets/providers/SingleProvider.java delete mode 100644 src/assets/java/speiger/src/coreengine/assets/simple/SimpleAssetProvider.java diff --git a/.settings/org.eclipse.buildship.core.prefs b/.settings/org.eclipse.buildship.core.prefs index 8171053..76702c8 100644 --- a/.settings/org.eclipse.buildship.core.prefs +++ b/.settings/org.eclipse.buildship.core.prefs @@ -5,7 +5,7 @@ connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER) connection.project.dir= eclipse.preferences.version=1 gradle.user.home= -java.home=C\:/Program Files/Eclipse Adoptium/jdk25 +java.home=C\:/Program Files/Java/jdk25 jvm.arguments= offline.mode=false override.workspace.settings=true diff --git a/build.gradle b/build.gradle index bd66764..de6bf97 100644 --- a/build.gradle +++ b/build.gradle @@ -77,6 +77,7 @@ dependencies { implementation "org.lwjgl:lwjgl-freetype::$lwjglNatives" implementation "org.lwjgl:lwjgl-harfbuzz::$lwjglNatives" implementation "org.lwjgl:lwjgl-nanovg::$lwjglNatives" + implementation "org.jspecify:jspecify:1.0.0" //Gson implementation 'com.google.code.gson:gson:2.8.6' diff --git a/src/assets/java/speiger/src/coreengine/assets/api/IAsset.java b/src/assets/java/speiger/src/coreengine/assets/api/IAsset.java index f38f8aa..1ff182e 100644 --- a/src/assets/java/speiger/src/coreengine/assets/api/IAsset.java +++ b/src/assets/java/speiger/src/coreengine/assets/api/IAsset.java @@ -1,14 +1,30 @@ package speiger.src.coreengine.assets.api; +import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.channels.SeekableByteChannel; +import java.util.List; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; + +import speiger.src.coreengine.assets.impl.parsers.Buffers; +import speiger.src.coreengine.assets.impl.parsers.Closables; +import speiger.src.coreengine.assets.impl.parsers.Serialized; public interface IAsset { public IAssetPackage owner(); public ID location(); - public String hash(); public InputStream stream() throws IOException; + public default ByteBuffer bytes() throws IOException { return parse(Buffers.HEAP); } + public default BufferedReader reader() throws IOException { return parse(Closables.READER); } + public default SeekableByteChannel channel() throws IOException { return parse(Closables.BYTE_CHANNEL); } + public default List lines() throws IOException { return parse(Serialized.LINES); } + public default JsonObject jsonObj() throws IOException { return parse(Serialized.JSON_OBJ); } + public default JsonArray jsonArray() throws IOException { return parse(Serialized.JSON_ARRAY); } public T parse(IAssetParser parser) throws IOException; } diff --git a/src/assets/java/speiger/src/coreengine/assets/api/IAssetPackage.java b/src/assets/java/speiger/src/coreengine/assets/api/IAssetPackage.java index 07355fd..a20344f 100644 --- a/src/assets/java/speiger/src/coreengine/assets/api/IAssetPackage.java +++ b/src/assets/java/speiger/src/coreengine/assets/api/IAssetPackage.java @@ -1,18 +1,38 @@ package speiger.src.coreengine.assets.api; import java.io.IOException; -import java.nio.file.WatchService; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.FileTime; import java.util.List; -import java.util.Map; -import java.util.Optional; import java.util.function.BiConsumer; +import java.util.function.Consumer; -import speiger.src.coreengine.assets.AssetLocation; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +import speiger.src.coreengine.assets.impl.FolderWatcher.ChangeType; + +@NullMarked public interface IAssetPackage extends AutoCloseable { - void watchService(WatchService listener) throws IOException; + void registerWatchers(IFileWatcher watcher) throws IOException; + boolean handleChange(Path file, ChangeType type, Consumer changed) throws IOException; List domains(); - Map fileHashes(); - Optional get(AssetLocation location); - void getAll(AssetLocation folder, BiConsumer assets); + @Nullable + IAsset get(ID location); + void getAll(ID folder, BiConsumer assets); + + public record AssetIdentity(long size, FileTime lastModified) { + + public AssetIdentity(Path path) throws IOException { + BasicFileAttributes attributes = Files.readAttributes(path, BasicFileAttributes.class); + this(attributes.size(), attributes.lastModifiedTime()); + } + + public boolean matches(Path path) throws IOException { + BasicFileAttributes attributes = Files.readAttributes(path, BasicFileAttributes.class); + return size() == attributes.size() && lastModified().equals(attributes.lastModifiedTime()); + } + } } diff --git a/src/assets/java/speiger/src/coreengine/assets/api/IAssetProvider.java b/src/assets/java/speiger/src/coreengine/assets/api/IAssetProvider.java index ba1e5d6..330b536 100644 --- a/src/assets/java/speiger/src/coreengine/assets/api/IAssetProvider.java +++ b/src/assets/java/speiger/src/coreengine/assets/api/IAssetProvider.java @@ -1,16 +1,64 @@ package speiger.src.coreengine.assets.api; +import java.util.List; import java.util.Map; +import java.util.Set; import java.util.function.Predicate; -public interface IAssetProvider { +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +import speiger.src.collections.objects.lists.ObjectArrayList; +import speiger.src.collections.objects.lists.ObjectList; +import speiger.src.collections.objects.utils.ObjectLists; + +@NullMarked +public interface IAssetProvider extends AutoCloseable { + @Nullable IAsset get(ID id); + @Nullable MultiAsset getAll(ID id); - String hash(ID id); Map list(String folder, Predicate filter); Map listAll(String folder, Predicate filter); - public interface IReloadableAssetProvider extends IAssetProvider, AutoCloseable { - void addReloadListener(Runnable run); + public static class ReloadTracker { + ObjectList listeners = new ObjectArrayList<>(); + + public void register(IResourceListener listener) { + listeners.add(new Listener(listener)); + } + + public void register(Predicate filter, IResourceListener listener) { + listeners.add(new Listener(filter, listener)); + } + + public List findByFilter(Set changed) { + if(changed.isEmpty()) return ObjectLists.empty(); + List result = new ObjectArrayList<>(); + for(int i = 0,m=listeners.size();i listeners() { + return listeners.map(Listener::listener).pourAsList(); + } + + private record Listener(Predicate filter, IResourceListener listener) { + public Listener(IResourceListener listener) { + this(_ -> true, listener); + } + + public boolean contains(ID id) { + return filter.test(id); + } + } } } diff --git a/src/assets/java/speiger/src/coreengine/assets/api/ID.java b/src/assets/java/speiger/src/coreengine/assets/api/ID.java index 88f3f21..9e8d3ac 100644 --- a/src/assets/java/speiger/src/coreengine/assets/api/ID.java +++ b/src/assets/java/speiger/src/coreengine/assets/api/ID.java @@ -1,21 +1,21 @@ package speiger.src.coreengine.assets.api; +import java.nio.file.Path; import java.util.Objects; public record ID(String domain, String path) implements Comparable { public ID { - Objects.requireNonNull(domain); - Objects.requireNonNull(path); - if(!isValidDomain(domain)) throw new IllegalArgumentException("Non [a-zA-Z0-9_.-] Character found in domain of location ["+domain+":"+path+"]"); - if(!isValidPath(path)) throw new IllegalArgumentException("Non [a-zA-Z0-9/_.-] Character found in path of location ["+domain+":"+path+"]"); + if(!isValidDomain(Objects.requireNonNull(domain))) throw new IllegalArgumentException("Non [a-zA-Z0-9_.-] Character found in domain of location ["+domain+":"+path+"]"); + if(!isValidPath(Objects.requireNonNull(path))) throw new IllegalArgumentException("Non [a-zA-Z0-9/_.-] Character found in path of location ["+domain+":"+path+"]"); } public ID prefix(String prefix) { return of(domain, prefix+"/"+path); } public ID suffix(String suffix) { return of(domain, path+"/"+suffix); } + public ID suffix(Path path) { return suffix(path.toString().replace("\\", "/")); } public ID alternate(String alterversion) { return of(domain, path+"."+alterversion); } - public String fileLocation() { return "assets/"+domain+"/"+path; } + public String fileLocation() { return domain+"/"+path; } public boolean isRoot() { return !path.contains("/"); } public boolean isRootFolder(String folder) { return path.indexOf('/', folder.length()+1) < 0; } public boolean endsWith(String suffix) { return path.endsWith(suffix); } @@ -35,11 +35,11 @@ public record ID(String domain, String path) implements Comparable { return id.domain.equals(domain) && id.path.equals(path); } - public static ID of(String domain, String path) { + public static final ID of(String domain, String path) { return new ID((domain == null || domain.isBlank() ? "base" : domain), path); } - public static ID of(String location) { + public static final ID of(String location) { Objects.requireNonNull(location); int index = location.indexOf(":"); String domain = location.substring(0, index); @@ -49,10 +49,13 @@ public record ID(String domain, String path) implements Comparable { return new ID(domain, path); } + public static final ID of(Path path) { + return of(path.getName(0).toString(), path.subpath(1, path.getNameCount()).toString().replace("\\", "/")); + } + public static final ID tryOf(String location) { try { return of(location); } - catch(Exception e) {} - return null; + catch(Exception e) { return null; } } private static boolean isValidDomain(String s) { diff --git a/src/assets/java/speiger/src/coreengine/assets/api/IFileWatcher.java b/src/assets/java/speiger/src/coreengine/assets/api/IFileWatcher.java new file mode 100644 index 0000000..69f6d64 --- /dev/null +++ b/src/assets/java/speiger/src/coreengine/assets/api/IFileWatcher.java @@ -0,0 +1,20 @@ +package speiger.src.coreengine.assets.api; + +import java.io.IOException; +import java.nio.file.Path; + +public interface IFileWatcher { + void addDirectory(Path basePath) throws IOException; + void addFile(Path file) throws IOException; + + public static record FileWatcher(IOConsumer folder, IOConsumer file) implements IFileWatcher { + @Override + public void addDirectory(Path basePath) throws IOException { folder().accept(basePath); } + @Override + public void addFile(Path file) throws IOException { file().accept(file); } + } + + public static interface IOConsumer { + void accept(T t) throws IOException; + } +} diff --git a/src/assets/java/speiger/src/coreengine/assets/api/IResourceListener.java b/src/assets/java/speiger/src/coreengine/assets/api/IResourceListener.java new file mode 100644 index 0000000..d0b3858 --- /dev/null +++ b/src/assets/java/speiger/src/coreengine/assets/api/IResourceListener.java @@ -0,0 +1,13 @@ +package speiger.src.coreengine.assets.api; + +import java.util.concurrent.CompletableFuture; + +public interface IResourceListener { + void name(); + CompletableFuture reload(ReloadContext context, ISyncer sync); + void release(); + + public static interface ISyncer { + CompletableFuture sync(T value); + } +} diff --git a/src/assets/java/speiger/src/coreengine/assets/api/ReloadContext.java b/src/assets/java/speiger/src/coreengine/assets/api/ReloadContext.java new file mode 100644 index 0000000..0611965 --- /dev/null +++ b/src/assets/java/speiger/src/coreengine/assets/api/ReloadContext.java @@ -0,0 +1,11 @@ +package speiger.src.coreengine.assets.api; + +import java.util.Set; +import java.util.concurrent.Executor; + +public record ReloadContext(IAssetProvider provider, Set filter, Executor main, Executor background) { + + public boolean hasChanged(ID id) { + return filter.isEmpty() || filter.contains(id); + } +} diff --git a/src/assets/java/speiger/src/coreengine/assets/impl/FolderAssetPackage.java b/src/assets/java/speiger/src/coreengine/assets/impl/FolderAssetPackage.java index fad585b..91b31e4 100644 --- a/src/assets/java/speiger/src/coreengine/assets/impl/FolderAssetPackage.java +++ b/src/assets/java/speiger/src/coreengine/assets/impl/FolderAssetPackage.java @@ -4,29 +4,67 @@ import java.io.IOException; import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.WatchService; +import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.stream.Stream; import speiger.src.collections.objects.lists.ObjectArrayList; import speiger.src.collections.objects.utils.ObjectLists; -import speiger.src.coreengine.assets.AssetLocation; import speiger.src.coreengine.assets.api.IAsset; import speiger.src.coreengine.assets.api.IAssetPackage; import speiger.src.coreengine.assets.api.ID; +import speiger.src.coreengine.assets.api.IFileWatcher; +import speiger.src.coreengine.assets.impl.FolderWatcher.ChangeType; public class FolderAssetPackage implements IAssetPackage { + Map knownIdentities = new HashMap<>(); Path basePath; - ID id; public FolderAssetPackage(Path basePath) { this.basePath = basePath; } - + @Override - public void watchService(WatchService listener) throws IOException { + public void registerWatchers(IFileWatcher watcher) throws IOException { + initCache(); + watcher.addDirectory(basePath); + } + + private void initCache() throws IOException { + for(Path path : Files.walk(basePath).filter(Files::isRegularFile).toList()) { + knownIdentities.put(ID.of(basePath.relativize(path)), new AssetIdentity(path)); + } + } + + public boolean handleChange(Path path, ChangeType type, Consumer changedIds) throws IOException { + if(Files.notExists(basePath)) return false; + Path target = basePath.relativize(path); + ID id = ID.of(target); + switch(type) { + case ADD: + if(Files.isRegularFile(path)) { + knownIdentities.put(id, new AssetIdentity(path)); + changedIds.accept(id); + } + break; + case MODIFIED: + AssetIdentity original = knownIdentities.get(id); + if(original != null && !original.matches(path)) { + knownIdentities.put(id, new AssetIdentity(path)); + changedIds.accept(id); + } + break; + case REMOVE: { + if(knownIdentities.remove(id) != null) { + changedIds.accept(id); + } + break; + } + } + return true; } @Override @@ -34,7 +72,7 @@ public class FolderAssetPackage implements IAssetPackage { @Override public List domains() { - try(DirectoryStream dirs = Files.newDirectoryStream(basePath.resolve("assets"))) { + try(DirectoryStream dirs = Files.newDirectoryStream(basePath)) { List domains = new ObjectArrayList<>(); dirs.forEach(T -> domains.add(T.getFileName().toString())); return domains; @@ -46,19 +84,24 @@ public class FolderAssetPackage implements IAssetPackage { } @Override - public Map fileHashes() { - return null; + public IAsset get(ID location) { + Path path = basePath.resolve(location.fileLocation()); + return Files.notExists(path) ? null : new SimpleAsset(this, location, path); } @Override - public Optional get(AssetLocation location) { - Path path = basePath.resolve(location.actualLocation()); - return Files.notExists(path) ? Optional.empty() : Optional.of(new SimpleAsset(this, id, path, null)); - } - - @Override - public void getAll(AssetLocation folder, BiConsumer assets) { - + public void getAll(ID folder, BiConsumer assets) { + Path start = basePath.resolve(folder.fileLocation()); + if(Files.notExists(start)) return; + try(Stream stream = Files.walk(start).filter(Files::isRegularFile)) { + for(Path path : (Iterable)() -> stream.iterator()) { + ID id = folder.suffix(start.relativize(path)); + assets.accept(folder, new SimpleAsset(this, id, path)); + } + } + catch(Exception e) { + e.printStackTrace(); + } } } diff --git a/src/assets/java/speiger/src/coreengine/assets/impl/FolderWatcher.java b/src/assets/java/speiger/src/coreengine/assets/impl/FolderWatcher.java new file mode 100644 index 0000000..3d54a13 --- /dev/null +++ b/src/assets/java/speiger/src/coreengine/assets/impl/FolderWatcher.java @@ -0,0 +1,110 @@ +package speiger.src.coreengine.assets.impl; + +import java.io.IOException; +import java.nio.file.FileSystems; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.LinkOption; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.StandardWatchEventKinds; +import java.nio.file.WatchEvent; +import java.nio.file.WatchEvent.Kind; +import java.nio.file.WatchKey; +import java.nio.file.WatchService; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.Map; +import java.util.function.Predicate; + +import speiger.src.collections.objects.maps.impl.hash.Object2ObjectOpenHashMap; + +public class FolderWatcher { + WatchService watcher; + Map> keys = new Object2ObjectOpenHashMap<>(); + + public FolderWatcher() throws IOException { + watcher = FileSystems.getDefault().newWatchService(); + } + + public FolderWatcher(WatchService service) { + this.watcher = service; + } + + public void reset() { + keys.keySet().forEach(WatchKey::cancel); + keys.clear(); + } + + public void close() throws IOException { + watcher.close(); + keys.clear(); + } + + public void register(Path basePath, T owner, boolean recursive) throws IOException { + if(recursive) { + Files.walkFileTree(basePath, new SimpleFileVisitor() { + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + keys.put(register(dir), new PathFlags<>(dir, owner, true)); + return FileVisitResult.CONTINUE; + } + }); + return; + } + keys.put(register(basePath), new PathFlags<>(basePath, owner, recursive)); + } + + private WatchKey register(Path path) throws IOException { + return path.register(watcher, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_DELETE); + } + + public void pollEvents(FolderListener output) { + WatchKey key = null; + boolean checkInvalidation = false; + while((key = watcher.poll()) != null) { + PathFlags flags = keys.get(key); + if(flags == null) continue; + for(WatchEvent event : key.pollEvents()) { + if(event.kind() == StandardWatchEventKinds.OVERFLOW) continue; + Path child = flags.resolve((Path)event.context()); + ChangeType type = toType(event.kind()); + if(type == ChangeType.ADD && flags.recursion() && Files.isDirectory(child, LinkOption.NOFOLLOW_LINKS)) { + try { register(child, flags.owner(), true); } + catch(Exception e) { e.printStackTrace(); } + } + else if(type == ChangeType.REMOVE && !checkInvalidation) { + checkInvalidation = true; + } + output.onChanged(child, type, flags.owner()); + } + if(!key.reset()) { + keys.remove(key); + } + } + if(checkInvalidation) { + keys.keySet().removeIf(((Predicate)WatchKey::isValid).negate()); + } + } + + private ChangeType toType(Kind kind) { + if(kind == StandardWatchEventKinds.ENTRY_CREATE) return ChangeType.ADD; + else if(kind == StandardWatchEventKinds.ENTRY_MODIFY) return ChangeType.MODIFIED; + return ChangeType.REMOVE; + } + + public interface FolderListener { + public void onChanged(Path path, ChangeType type, T owner); + } + + private record PathFlags(Path folder, T owner, boolean recursion) { + public Path resolve(Path subFolder) { + return folder.resolve(subFolder); + } + } + + public static enum ChangeType { + ADD, + MODIFIED, + REMOVE; + } +} diff --git a/src/assets/java/speiger/src/coreengine/assets/impl/ReloadManager.java b/src/assets/java/speiger/src/coreengine/assets/impl/ReloadManager.java new file mode 100644 index 0000000..8428bae --- /dev/null +++ b/src/assets/java/speiger/src/coreengine/assets/impl/ReloadManager.java @@ -0,0 +1,119 @@ +package speiger.src.coreengine.assets.impl; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; + +import org.jspecify.annotations.Nullable; + +import speiger.src.collections.objects.lists.ObjectArrayList; +import speiger.src.collections.objects.maps.interfaces.Object2ObjectMap; +import speiger.src.collections.objects.sets.ObjectOpenHashSet; +import speiger.src.collections.objects.utils.ObjectLists; +import speiger.src.coreengine.assets.api.IAssetPackage; +import speiger.src.coreengine.assets.api.IAssetProvider.ReloadTracker; +import speiger.src.coreengine.assets.api.ID; +import speiger.src.coreengine.assets.api.IFileWatcher.FileWatcher; +import speiger.src.coreengine.assets.api.IResourceListener; +import speiger.src.coreengine.assets.impl.FolderWatcher.ChangeType; + +public class ReloadManager { + private static final ScopedValue CURRENT_PACKAGE = ScopedValue.newInstance(); + private static final ScopedValue> TRACKED = ScopedValue.newInstance(); + Map> trackers; + FolderWatcher watcher; + Runnable onStart; + Consumer onNewFound; + Consumer onRemoved; + + public ReloadManager(Runnable onStart, Consumer onNewFound, Consumer onRemoved) { + this.onStart = onStart; + this.onNewFound = onNewFound; + this.onRemoved = onRemoved; + } + + public void enableReload() { + if(trackers != null) return; + try { + watcher = new FolderWatcher<>(); + trackers = Object2ObjectMap.builder().map(); + if(onStart != null) onStart.run(); + } + catch(Exception e) { e.printStackTrace(); } + } + + public void disableReload() { + if(trackers == null) return; + try { watcher.close(); } + catch(Exception e) { e.printStackTrace(); } + watcher = null; + trackers = null; + } + + public void reset() { + if(trackers == null) return; + trackers.clear(); + watcher.reset(); + } + + public void registerPackage(IAssetPackage pack) { + try { ScopedValue.where(CURRENT_PACKAGE, pack).call(this::registerWatchers); } + catch(Exception e) { e.printStackTrace(); } + } + + public List handleFileEvents(ReloadTracker listeners, @Nullable Set changedIds) { + if(watcher == null) return ObjectLists.empty(); + if(changedIds == null) changedIds = new ObjectOpenHashSet<>(); + ScopedValue.where(TRACKED, changedIds).run(() -> watcher.pollEvents(this::handleEvent)); + return changedIds.isEmpty() ? ObjectLists.empty() : listeners.findByFilter(changedIds); + } + + protected void handleEvent(Path path, ChangeType type, IAssetPackage owner) { + if(owner == null) { + owner = findOwner(path); + if(owner == null) { + if(type == ChangeType.ADD && onNewFound != null) onNewFound.accept(path); + return; + } + } + try { + List changes = new ObjectArrayList<>(); + if(owner.handleChange(path, type, changes::add)) { + TRACKED.get().addAll(changes); + return; + } + } + catch(Exception e) { e.printStackTrace(); } + if(onRemoved != null) onRemoved.accept(owner); + } + + protected IAssetPackage findOwner(Path path) { + Map packages = trackers.get(path.getParent()); + return packages == null ? null : packages.get(path.getFileName()); + } + + private Void registerWatchers() throws IOException { + if(!CURRENT_PACKAGE.isBound()) throw new IllegalStateException("Current Package is required!"); + CURRENT_PACKAGE.get().registerWatchers(new FileWatcher(this::onFolderChanged, this::onFileChange)); + return null; + } + + private void onFileChange(Path file) throws IOException { + if(!CURRENT_PACKAGE.isBound()) throw new IllegalStateException("Current Package is required!"); + Map packages = trackers.get(file.getParent()); + if(packages == null) { + packages = Object2ObjectMap.builder().map(); + trackers.put(file.getParent(), packages); + watcher.register(file, null, false); + } + packages.put(file.getFileName(), CURRENT_PACKAGE.get()); + } + + private void onFolderChanged(Path folder) throws IOException { + if(!CURRENT_PACKAGE.isBound()) throw new IllegalStateException("Current Package is required!"); + watcher.register(folder, CURRENT_PACKAGE.get(), true); + } +} diff --git a/src/assets/java/speiger/src/coreengine/assets/impl/SimpleAsset.java b/src/assets/java/speiger/src/coreengine/assets/impl/SimpleAsset.java index a2e981a..7a921f4 100644 --- a/src/assets/java/speiger/src/coreengine/assets/impl/SimpleAsset.java +++ b/src/assets/java/speiger/src/coreengine/assets/impl/SimpleAsset.java @@ -10,7 +10,7 @@ import speiger.src.coreengine.assets.api.IAssetPackage; import speiger.src.coreengine.assets.api.IAssetParser; import speiger.src.coreengine.assets.api.ID; -public record SimpleAsset(IAssetPackage owner, ID location, Path path, String hash) implements IAsset { +public record SimpleAsset(IAssetPackage owner, ID location, Path path) implements IAsset { @Override public IAssetPackage owner() { diff --git a/src/assets/java/speiger/src/coreengine/assets/impl/ZipAssetPackage.java b/src/assets/java/speiger/src/coreengine/assets/impl/ZipAssetPackage.java new file mode 100644 index 0000000..7b2308f --- /dev/null +++ b/src/assets/java/speiger/src/coreengine/assets/impl/ZipAssetPackage.java @@ -0,0 +1,129 @@ +package speiger.src.coreengine.assets.impl; + +import java.io.IOException; +import java.nio.file.DirectoryStream; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +import speiger.src.collections.objects.lists.ObjectArrayList; +import speiger.src.collections.objects.maps.impl.hash.Object2LongOpenHashMap; +import speiger.src.collections.objects.maps.interfaces.Object2LongMap; +import speiger.src.collections.objects.utils.ObjectLists; +import speiger.src.coreengine.assets.api.IAsset; +import speiger.src.coreengine.assets.api.IAssetPackage; +import speiger.src.coreengine.assets.api.ID; +import speiger.src.coreengine.assets.api.IFileWatcher; +import speiger.src.coreengine.assets.impl.FolderWatcher.ChangeType; + +public class ZipAssetPackage implements IAssetPackage { + Object2LongMap crcCache = new Object2LongOpenHashMap<>(); + Path baseFile; + String startFolder; + FileSystem cache; + + public ZipAssetPackage(Path baseFile, String startFolder) { + this.baseFile = baseFile; + this.startFolder = startFolder; + } + + @Override + public void close() throws Exception { + if(cache == null) return; + FileSystem cache = this.cache; + this.cache = null; + cache.close(); + } + + protected FileSystem getReference() throws IOException { + if(cache == null) cache = FileSystems.newFileSystem(baseFile); + return cache; + } + + @Override + public void registerWatchers(IFileWatcher watcher) throws IOException { + initCache(); + watcher.addFile(baseFile); + } + + private void initCache() { + try(AutoCloseable _ = this) { + Path root = getReference().getPath(startFolder); + for(Path path : Files.walk(root).filter(Files::isRegularFile).toList()) { + crcCache.put(ID.of(root.relativize(path)), getCrc(path)); + } + } + catch(Exception e) { + e.printStackTrace(); + } + } + + private long getCrc(Path path) throws IOException { + return (Long)Files.readAttributes(path, "crc").get("crc"); + } + + @Override + public boolean handleChange(Path file, ChangeType type, Consumer changed) throws IOException { + if(!baseFile.equals(file)) return true; + if(Files.notExists(baseFile)) return false; + if(type == ChangeType.MODIFIED) { + try { + Path root = getReference().getPath(startFolder); + for(Path path : Files.walk(root).filter(Files::isRegularFile).toList()) { + ID id = ID.of(root.relativize(path)); + long fileCRC = getCrc(path); + if(crcCache.getOrDefault(id, -1L) != fileCRC) { + crcCache.put(id, fileCRC); + changed.accept(id); + } + } + } + catch(Exception e) { + e.printStackTrace(); + } + return true; + } + return false; + } + + @Override + public List domains() { + try(AutoCloseable _ = this; DirectoryStream dirs = Files.newDirectoryStream(getReference().getPath(startFolder))) { + List domains = new ObjectArrayList<>(); + dirs.forEach(T -> domains.add(T.getFileName().toString())); + return domains; + } + catch(Exception e) { + e.printStackTrace(); + return ObjectLists.empty(); + } + } + + @Override + public IAsset get(ID location) { + try { + Path path = getReference().getPath(startFolder, location.fileLocation()); + return Files.notExists(path) ? null : new SimpleAsset(this, location, path); + } + catch(Exception e) { e.printStackTrace(); } + return null; + } + + @Override + public void getAll(ID folder, BiConsumer assets) { + try { + Path root = getReference().getPath(startFolder, folder.fileLocation()); + if(Files.notExists(root)) return; + for(Path path : Files.walk(root).filter(Files::isRegularFile).toList()) { + ID id = ID.of(root.relativize(path)); + assets.accept(id, new SimpleAsset(this, id, path)); + } + } + catch(Exception e) { e.printStackTrace(); } + } + +} diff --git a/src/assets/java/speiger/src/coreengine/assets/impl/parsers/Buffers.java b/src/assets/java/speiger/src/coreengine/assets/impl/parsers/Buffers.java new file mode 100644 index 0000000..c338140 --- /dev/null +++ b/src/assets/java/speiger/src/coreengine/assets/impl/parsers/Buffers.java @@ -0,0 +1,33 @@ +package speiger.src.coreengine.assets.impl.parsers; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.lwjgl.system.MemoryUtil; + +import speiger.src.coreengine.assets.api.IAssetParser; + +public class Buffers { + public static final IAssetParser HEAP = new Heap(); + public static final IAssetParser DIRECT = new Direct(); + + public static class Heap implements IAssetParser { + @Override + public ByteBuffer parse(Path path) throws IOException { + return ByteBuffer.wrap(Files.readAllBytes(path)); + } + } + + public static class Direct implements IAssetParser { + @Override + public ByteBuffer parse(Path path) throws IOException { + byte[] data = Files.readAllBytes(path); + ByteBuffer buffer = MemoryUtil.memAlloc(data.length); + buffer.put(data); + buffer.flip(); + return buffer; + } + } +} diff --git a/src/assets/java/speiger/src/coreengine/assets/impl/parsers/Closables.java b/src/assets/java/speiger/src/coreengine/assets/impl/parsers/Closables.java new file mode 100644 index 0000000..b8eba07 --- /dev/null +++ b/src/assets/java/speiger/src/coreengine/assets/impl/parsers/Closables.java @@ -0,0 +1,29 @@ +package speiger.src.coreengine.assets.impl.parsers; + +import java.io.BufferedReader; +import java.io.IOException; +import java.nio.channels.SeekableByteChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; + +import speiger.src.coreengine.assets.api.IAssetParser; + +public class Closables { + public static final IAssetParser READER = new Reader(); + public static final IAssetParser BYTE_CHANNEL = new ByteChannel(); + + public static class Reader implements IAssetParser { + @Override + public BufferedReader parse(Path path) throws IOException { + return Files.newBufferedReader(path); + } + } + + public static class ByteChannel implements IAssetParser { + @Override + public SeekableByteChannel parse(Path path) throws IOException { + return Files.newByteChannel(path, StandardOpenOption.READ); + } + } +} diff --git a/src/assets/java/speiger/src/coreengine/assets/impl/parsers/Serialized.java b/src/assets/java/speiger/src/coreengine/assets/impl/parsers/Serialized.java new file mode 100644 index 0000000..86e80e9 --- /dev/null +++ b/src/assets/java/speiger/src/coreengine/assets/impl/parsers/Serialized.java @@ -0,0 +1,44 @@ +package speiger.src.coreengine.assets.impl.parsers; + +import java.io.BufferedReader; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +import speiger.src.coreengine.assets.api.IAssetParser; + +public class Serialized { + public static final IAssetParser> LINES = new Lines(); + public static final IAssetParser JSON_OBJ = new JsonObj(); + public static final IAssetParser JSON_ARRAY = new JsonList(); + + public static class Lines implements IAssetParser> { + @Override + public List parse(Path path) throws IOException { + return Files.readAllLines(path); + } + } + + public static class JsonObj implements IAssetParser { + @Override + public JsonObject parse(Path path) throws IOException { + try(BufferedReader reader = Files.newBufferedReader(path)) { + return JsonParser.parseReader(reader).getAsJsonObject(); + } + } + } + + public static class JsonList implements IAssetParser { + @Override + public JsonArray parse(Path path) throws IOException { + try(BufferedReader reader = Files.newBufferedReader(path)) { + return JsonParser.parseReader(reader).getAsJsonArray(); + } + } + } +} diff --git a/src/assets/java/speiger/src/coreengine/assets/manager/AssetManager.java b/src/assets/java/speiger/src/coreengine/assets/manager/AssetManager.java new file mode 100644 index 0000000..5e0cebb --- /dev/null +++ b/src/assets/java/speiger/src/coreengine/assets/manager/AssetManager.java @@ -0,0 +1,118 @@ +package speiger.src.coreengine.assets.manager; + +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Predicate; + +import org.jspecify.annotations.Nullable; + +import speiger.src.collections.objects.sets.ObjectOpenHashSet; +import speiger.src.coreengine.assets.api.IAsset; +import speiger.src.coreengine.assets.api.IAssetPackage; +import speiger.src.coreengine.assets.api.IAssetProvider; +import speiger.src.coreengine.assets.api.IAssetProvider.ReloadTracker; +import speiger.src.coreengine.assets.api.ID; +import speiger.src.coreengine.assets.api.IResourceListener; +import speiger.src.coreengine.assets.api.MultiAsset; +import speiger.src.coreengine.assets.impl.ReloadManager; +import speiger.src.coreengine.assets.providers.BaseProvider; +import speiger.src.coreengine.assets.providers.LayeredProvider; +import speiger.src.coreengine.assets.providers.SingleProvider; + +public class AssetManager { + BaseProvider provider; + List packages; + ReloadTracker tracker = new ReloadTracker(); + ReloadManager manager; + + public AssetManager(BaseProvider provider) { + setPackages(provider); + } + + public static AssetManager single(IAssetPackage pack) { + return new AssetManager(new SingleProvider(pack)); + } + + public static AssetManager mult(List packages) { + return new AssetManager(new LayeredProvider(packages)); + } + + private void setPackages(BaseProvider provider) { + this.provider = provider; + this.packages = provider.packages(); + if(manager != null) { + manager.reset(); + packages.forEach(manager::registerPackage); + } + } + + public AssetManager withReload(@Nullable Consumer onNewFound, @Nullable Consumer onRemoval) { + if(manager != null) throw new IllegalStateException("Can't replace the reload system"); + manager = new ReloadManager(null, onNewFound, onRemoval); + manager.enableReload(); + packages.forEach(manager::registerPackage); + return this; + } + + public void register(IResourceListener listener) { + tracker.register(listener); + } + + public void register(Predicate filter, IResourceListener listener) { + tracker.register(filter, listener); + } + + public ReloadTracker listeners() { + return tracker; + } + + public ReloadTask reload(ReloadBuilder builder) { + if(builder.packages.isPresent()) setPackages(builder.packages.get()); + if(builder.listeners.isEmpty()) builder.withListener(tracker.listeners()); + return new ReloadTask(get(), builder); + } + + public boolean isReloadable() { + return manager != null; + } + + @Nullable + public ReloadTask handleChanges(ReloadBuilder builder) { + if(manager == null) return null; + Set changed = new ObjectOpenHashSet<>(); + List reloading = manager.handleFileEvents(tracker, changed); + if(reloading.isEmpty()) return null; + return reload(builder.withFilter(changed).withPackages((BaseProvider)null).withListener(reloading)); + } + + public IAssetProvider get() { + return new WrappedProvider(provider); + } + + public final class WrappedProvider implements IAssetProvider { + BaseProvider provider; + + public WrappedProvider(BaseProvider provider) { + this.provider = provider; + } + + @Override + public void close() throws Exception { + BaseProvider provider = this.provider; + this.provider = null; + provider.close(); + } + + @Override + public @Nullable IAsset get(ID id) { return provider.get(id); } + @Override + public @Nullable MultiAsset getAll(ID id) { return provider.getAll(id); } + @Override + public Map list(String folder, Predicate filter) { return provider.list(folder, filter); } + @Override + public Map listAll(String folder, Predicate filter) { return provider.listAll(folder, filter); } + } +} diff --git a/src/assets/java/speiger/src/coreengine/assets/manager/ReloadBuilder.java b/src/assets/java/speiger/src/coreengine/assets/manager/ReloadBuilder.java new file mode 100644 index 0000000..3670bdb --- /dev/null +++ b/src/assets/java/speiger/src/coreengine/assets/manager/ReloadBuilder.java @@ -0,0 +1,56 @@ +package speiger.src.coreengine.assets.manager; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.Executor; + +import speiger.src.collections.objects.utils.ObjectSets; +import speiger.src.coreengine.assets.api.IAssetPackage; +import speiger.src.coreengine.assets.api.ID; +import speiger.src.coreengine.assets.api.IResourceListener; +import speiger.src.coreengine.assets.providers.BaseProvider; +import speiger.src.coreengine.assets.providers.LayeredProvider; +import speiger.src.coreengine.assets.providers.SingleProvider; + +public class ReloadBuilder { + Executor mainRunner = Runnable::run; + Executor syncRunner = Runnable::run; + Set filter = ObjectSets.empty(); + Optional packages = Optional.empty(); + Optional> listeners = Optional.empty(); + + public ReloadBuilder withFilter(Set filter) { + this.filter = Objects.requireNonNull(filter); + return this; + } + + public ReloadBuilder withMain(Executor main) { + mainRunner = Objects.requireNonNull(main); + return this; + } + + public ReloadBuilder withSync(Executor sync) { + syncRunner = Objects.requireNonNull(sync); + return this; + } + + public ReloadBuilder withPackages(List packages) { + return withPackages(new LayeredProvider(packages)); + } + + public ReloadBuilder withPackages(IAssetPackage pack) { + return withPackages(new SingleProvider(pack)); + } + + public ReloadBuilder withPackages(BaseProvider packages) { + this.packages = Optional.ofNullable(packages); + return this; + } + + public ReloadBuilder withListener(List listeners) { + this.listeners = Optional.ofNullable(listeners); + return this; + } +} \ No newline at end of file diff --git a/src/assets/java/speiger/src/coreengine/assets/manager/ReloadTask.java b/src/assets/java/speiger/src/coreengine/assets/manager/ReloadTask.java new file mode 100644 index 0000000..15ea6b8 --- /dev/null +++ b/src/assets/java/speiger/src/coreengine/assets/manager/ReloadTask.java @@ -0,0 +1,72 @@ +package speiger.src.coreengine.assets.manager; + +import java.util.List; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicInteger; + +import speiger.src.collections.objects.lists.ObjectArrayList; +import speiger.src.collections.objects.sets.ObjectOpenHashSet; +import speiger.src.coreengine.assets.api.IAssetProvider; +import speiger.src.coreengine.assets.api.IResourceListener; +import speiger.src.coreengine.assets.api.IResourceListener.ISyncer; +import speiger.src.coreengine.assets.api.ReloadContext; + +public class ReloadTask { + AtomicInteger started = new AtomicInteger(); + AtomicInteger finished = new AtomicInteger(); + CompletableFuture future; + + public ReloadTask(IAssetProvider provider, ReloadBuilder builder) { + future = reload(new ReloadContext(provider, builder.filter, new CountingExecutor(builder.mainRunner, started, finished), new CountingExecutor(builder.syncRunner, started, finished)), new ObjectArrayList<>(builder.listeners.get())); + } + + public int started() { return started.get(); } + public int stopped() { return finished.get(); } + public float progress() { return finished.floatValue() / started.floatValue(); } + public CompletableFuture future() { return future; } + public void join() { future.join(); } + + private CompletableFuture reload(ReloadContext context, List listeners) { + started.getAndIncrement(); + Set todo = new ObjectOpenHashSet<>(listeners); + CompletableFuture syncFuture = new CompletableFuture<>(); + CompletableFuture chainedTask = CompletableFuture.completedFuture(null); + List> tasks = new ObjectArrayList<>(); + for(IResourceListener listener : listeners) { + CompletableFuture current = chainedTask; + chainedTask = listener.reload(context, new Syncer(context.main(), current, syncFuture, listener, todo)); + tasks.add(chainedTask); + } + return CompletableFuture.allOf(tasks.toArray(CompletableFuture[]::new)).thenRunAsync(() -> { + finished.getAndIncrement(); + try { context.provider().close(); } + catch(Exception e) { e.printStackTrace(); } + }, context.main()); + } + + + private record Syncer(Executor main, CompletableFuture currentStep, CompletableFuture syncStep, IResourceListener listener, Set toComplete) implements ISyncer { + @Override + public CompletableFuture sync(T value) { + main.execute(() -> { + toComplete.remove(listener); + if(toComplete.isEmpty()) syncStep.complete(null); + }); + return syncStep.thenCombine(currentStep, (_, _) -> value); + } + + } + + private record CountingExecutor(Executor wrapper, AtomicInteger started, AtomicInteger finished) implements Executor { + @Override + public void execute(Runnable command) { + started.getAndIncrement(); + wrapper.execute(() -> { + command.run(); + finished.getAndIncrement(); + }); + } + } +} diff --git a/src/assets/java/speiger/src/coreengine/assets/providers/BaseProvider.java b/src/assets/java/speiger/src/coreengine/assets/providers/BaseProvider.java new file mode 100644 index 0000000..5252684 --- /dev/null +++ b/src/assets/java/speiger/src/coreengine/assets/providers/BaseProvider.java @@ -0,0 +1,10 @@ +package speiger.src.coreengine.assets.providers; + +import java.util.List; + +import speiger.src.coreengine.assets.api.IAssetPackage; +import speiger.src.coreengine.assets.api.IAssetProvider; + +public interface BaseProvider extends IAssetProvider, AutoCloseable { + List packages(); +} diff --git a/src/assets/java/speiger/src/coreengine/assets/providers/LayeredProvider.java b/src/assets/java/speiger/src/coreengine/assets/providers/LayeredProvider.java new file mode 100644 index 0000000..e7a41cb --- /dev/null +++ b/src/assets/java/speiger/src/coreengine/assets/providers/LayeredProvider.java @@ -0,0 +1,137 @@ +package speiger.src.coreengine.assets.providers; + +import java.util.List; +import java.util.Map; +import java.util.function.Predicate; + +import org.jspecify.annotations.Nullable; + +import speiger.src.collections.objects.lists.ObjectArrayList; +import speiger.src.collections.objects.lists.ObjectList; +import speiger.src.collections.objects.maps.interfaces.Object2ObjectMap; +import speiger.src.collections.objects.utils.maps.Object2ObjectMaps; +import speiger.src.coreengine.assets.api.IAsset; +import speiger.src.coreengine.assets.api.IAssetPackage; +import speiger.src.coreengine.assets.api.IAssetProvider; +import speiger.src.coreengine.assets.api.ID; +import speiger.src.coreengine.assets.api.MultiAsset; + +public class LayeredProvider implements BaseProvider { + Map domains = Object2ObjectMap.builder().linkedMap(); + List packages; + + public LayeredProvider(List packages) { + this.packages = packages; + for(IAssetPackage pack : packages) { + for(String domain : pack.domains()) { + domains.computeIfAbsent(domain, DomainProvider::new).add(pack); + } + } + } + + @Override + public List packages() { + return packages; + } + + @Override + public void close() throws Exception { + for(IAssetPackage pack : packages) { + pack.close(); + } + } + + @Override + public @Nullable IAsset get(ID id) { + DomainProvider provider = domains.get(id.domain()); + return provider == null ? null : provider.get(id); + } + + @Override + public @Nullable MultiAsset getAll(ID id) { + DomainProvider provider = domains.get(id.domain()); + return provider == null ? null : provider.getAll(id); + } + + @Override + public Map list(String folder, Predicate filter) { + Map result = Object2ObjectMap.builder().linkedMap(); + for(DomainProvider provider : domains.values()) { + result.putAll(provider.list(folder, filter)); + } + return result; + } + + @Override + public Map listAll(String folder, Predicate filter) { + Map result = Object2ObjectMap.builder().linkedMap(); + for(DomainProvider provider : domains.values()) { + result.putAll(provider.listAll(folder, filter)); + } + return result; + } + + private static class DomainProvider implements IAssetProvider { + final String domain; + ObjectList packages = new ObjectArrayList<>(); + + public DomainProvider(String domain) { + this.domain = domain; + } + + public void add(IAssetPackage pack) { + packages.add(pack); + } + + @Override + public void close() {} + + @Override + public @Nullable IAsset get(ID id) { + for(int i = packages.size()-1;i>=0;i--) { + IAsset asset = packages.get(i).get(id); + if(asset != null) return asset; + } + return null; + } + + @Override + public @Nullable MultiAsset getAll(ID id) { + ObjectList result = new ObjectArrayList<>(); + for(int i = packages.size()-1;i>=0;i--) { + IAsset asset = packages.get(i).get(id); + if(asset != null) result.add(asset); + } + return new MultiAsset(result); + } + + @Override + public Map list(String folder, Predicate filter) { + ID base = ID.of(domain, folder); + Map result = Object2ObjectMap.builder().linkedMap(); + for(int i = packages.size()-1;i>=0;i--) { + packages.get(i).getAll(base, (K, V) -> { + if(filter.test(K)) result.putIfAbsent(K, V); + }); + } + return result; + } + + @Override + public Map listAll(String folder, Predicate filter) { + ID base = ID.of(domain, folder); + Object2ObjectMap> assets = Object2ObjectMap.builder().linkedMap(); + for(int i = packages.size()-1;i>=0;i--) { + packages.get(i).getAll(base, (K, V) -> { + if(filter.test(K)) assets.supplyIfAbsent(K, ObjectArrayList::new).add(V); + }); + } + Map result = Object2ObjectMap.builder().linkedMap(); + for(Object2ObjectMap.Entry> entry : Object2ObjectMaps.fastIterable(assets)) { + result.put(entry.getKey(), new MultiAsset(entry.getValue())); + } + return result; + } + + } +} diff --git a/src/assets/java/speiger/src/coreengine/assets/providers/SingleProvider.java b/src/assets/java/speiger/src/coreengine/assets/providers/SingleProvider.java new file mode 100644 index 0000000..afe2e18 --- /dev/null +++ b/src/assets/java/speiger/src/coreengine/assets/providers/SingleProvider.java @@ -0,0 +1,74 @@ +package speiger.src.coreengine.assets.providers; + +import java.util.List; +import java.util.Map; +import java.util.function.Predicate; + +import org.jspecify.annotations.Nullable; + +import speiger.src.collections.objects.lists.ObjectArrayList; +import speiger.src.collections.objects.lists.ObjectList; +import speiger.src.collections.objects.maps.interfaces.Object2ObjectMap; +import speiger.src.collections.objects.utils.ObjectLists; +import speiger.src.collections.objects.utils.maps.Object2ObjectMaps; +import speiger.src.coreengine.assets.api.IAsset; +import speiger.src.coreengine.assets.api.IAssetPackage; +import speiger.src.coreengine.assets.api.ID; +import speiger.src.coreengine.assets.api.MultiAsset; + +public class SingleProvider implements BaseProvider { + IAssetPackage pack; + List domains; + + public SingleProvider(IAssetPackage pack) { + this.pack = pack; + this.domains = pack.domains(); + } + + @Override + public void close() throws Exception { + pack.close(); + } + + @Override + public List packages() { + return ObjectLists.singleton(pack); + } + + @Override + public @Nullable IAsset get(ID id) { + return pack.get(id); + } + + @Override + public @Nullable MultiAsset getAll(ID id) { + IAsset asset = pack.get(id); + return asset == null ? null : new MultiAsset(asset); + } + + @Override + public Map list(String folder, Predicate filter) { + Map result = Object2ObjectMap.builder().linkedMap(); + for(String domain : domains) { + pack.getAll(ID.of(domain, folder), (K, V) -> { + if(filter.test(K)) result.putIfAbsent(K, V); + }); + } + return result; + } + + @Override + public Map listAll(String folder, Predicate filter) { + Object2ObjectMap> assets = Object2ObjectMap.builder().linkedMap(); + for(String domain : domains) { + pack.getAll(ID.of(domain, folder), (K, V) -> { + if(filter.test(K)) assets.supplyIfAbsent(K, ObjectArrayList::new).add(V); + }); + } + Map result = Object2ObjectMap.builder().linkedMap(); + for(Object2ObjectMap.Entry> entry : Object2ObjectMaps.fastIterable(assets)) { + result.put(entry.getKey(), new MultiAsset(entry.getValue())); + } + return result; + } +} \ No newline at end of file diff --git a/src/assets/java/speiger/src/coreengine/assets/simple/SimpleAssetProvider.java b/src/assets/java/speiger/src/coreengine/assets/simple/SimpleAssetProvider.java deleted file mode 100644 index a8eb831..0000000 --- a/src/assets/java/speiger/src/coreengine/assets/simple/SimpleAssetProvider.java +++ /dev/null @@ -1,38 +0,0 @@ -package speiger.src.coreengine.assets.simple; - -import java.util.Map; -import java.util.function.Predicate; - -import speiger.src.coreengine.assets.api.IAsset; -import speiger.src.coreengine.assets.api.IAssetProvider; -import speiger.src.coreengine.assets.api.ID; -import speiger.src.coreengine.assets.api.MultiAsset; - -public class SimpleAssetProvider implements IAssetProvider { - - @Override - public IAsset get(ID id) { - return null; - } - - @Override - public MultiAsset getAll(ID id) { - return null; - } - - @Override - public String hash(ID id) { - return null; - } - - @Override - public Map list(String folder, Predicate filter) { - return null; - } - - @Override - public Map listAll(String folder, Predicate filter) { - return null; - } - -}