commit e9d8fceb80d65a5e11eead88a63729b031afc2e4
parent 184c8892982dc4ec1bf159986235089ce6cb6f87
Author: Oshgnacknak <osh@oshgnacknak.de>
Date: Sat, 10 Apr 2021 16:48:26 +0200
Implement asynchronous renderer
Diffstat:
4 files changed, 233 insertions(+), 0 deletions(-)
diff --git a/src/main/java/de/oshgnacknak/create_ponder_wonder/CreatePonderWonder.java b/src/main/java/de/oshgnacknak/create_ponder_wonder/CreatePonderWonder.java
@@ -1,6 +1,7 @@
package de.oshgnacknak.create_ponder_wonder;
import de.oshgnacknak.create_ponder_wonder.commands.AllCommands;
+import net.minecraft.client.Minecraft;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.common.Mod;
import org.apache.logging.log4j.LogManager;
@@ -11,7 +12,15 @@ public class CreatePonderWonder {
public static final String MODID = "create_ponder_wonder";
public static final Logger LOGGER = LogManager.getLogger(MODID);
+ public static final PonderRenderer PONDER_RENDERER = new PonderRenderer();
+
public CreatePonderWonder() {
MinecraftForge.EVENT_BUS.addListener(AllCommands::register);
}
+
+ public static void chat(String msg) {
+ if (Minecraft.getInstance().player != null) {
+ Minecraft.getInstance().player.sendChatMessage(msg);
+ }
+ }
}
\ No newline at end of file
diff --git a/src/main/java/de/oshgnacknak/create_ponder_wonder/PonderRenderer.java b/src/main/java/de/oshgnacknak/create_ponder_wonder/PonderRenderer.java
@@ -0,0 +1,190 @@
+package de.oshgnacknak.create_ponder_wonder;
+
+import com.simibubi.create.foundation.ponder.PonderRegistry;
+import com.simibubi.create.foundation.ponder.PonderWonderUI;
+import io.netty.util.concurrent.GlobalEventExecutor;
+import io.netty.util.concurrent.Promise;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.renderer.texture.NativeImage;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+public class PonderRenderer {
+
+ private static final int FPS = 60;
+ private static final int MAX_FRAMES = 15;
+ private static final int MAX_PONDERS_AT_ONCE = 10;
+
+ private final Lock lock;
+ private final BlockingQueue<PonderWonderUI> pondersToRender;
+ private final AtomicInteger currentlyRendering;
+ private final Condition renderDone;
+
+ private ExecutorService executorService;
+ private boolean rendering;
+ private boolean allEnqueued;
+ private String basePath;
+
+ public PonderRenderer() {
+ lock = new ReentrantLock();
+ pondersToRender = new ArrayBlockingQueue<>(MAX_PONDERS_AT_ONCE);
+ currentlyRendering = new AtomicInteger();
+ renderDone = lock.newCondition();
+ }
+
+ public void start(String basePath) {
+ if (rendering) {
+ CreatePonderWonder.chat("Error: cannot be done twice");
+ throw new IllegalStateException("Cannot start rendering twice");
+ }
+
+ rendering = true;
+ this.basePath = basePath;
+ currentlyRendering.set(0);
+ pondersToRender.clear();
+
+ executorService = Executors.newCachedThreadPool();
+ executorService.submit(this::renderPonders);
+ executorService.submit(this::enqueueAllPonders);
+
+ CreatePonderWonder.LOGGER.info("Started rendering ponders");
+ CreatePonderWonder.chat("Started rendering ponders");
+ }
+
+ public void stop() {
+ if (!rendering) {
+ CreatePonderWonder.chat("Aleady stopped...");
+ return;
+ }
+
+ CreatePonderWonder.LOGGER.warn("Stopping rendering ponders abruptly");
+ CreatePonderWonder.chat("Stopping rendering ponders abruptly");
+
+ try {
+ executorService.shutdown();
+ while (!executorService.awaitTermination(3, TimeUnit.SECONDS)) {
+ executorService.shutdownNow();
+ }
+ rendering = false;
+
+ CreatePonderWonder.LOGGER.info("Stopped rendering ponders");
+ CreatePonderWonder.chat("Stopped rendering ponders");
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private void renderPonders() {
+ try {
+ while (rendering) {
+ if (currentlyRendering.get() < MAX_PONDERS_AT_ONCE) {
+ renderPonder(pondersToRender.take());
+ } else {
+ renderDone.wait();
+ }
+
+ if (allEnqueued) {
+ rendering = false;
+ CreatePonderWonder.LOGGER.info("All ponders rendered: {}", basePath);
+ CreatePonderWonder.chat("All ponders rendered: " + basePath);
+ return;
+ }
+ }
+ } catch (Exception e) {
+ CreatePonderWonder.chat("Error: " + e.getMessage());
+ CreatePonderWonder.LOGGER.error("Exception whilst rendering ponders", e);
+ }
+ }
+
+ private void renderPonder(PonderWonderUI ponder) {
+ lock.lock();
+
+ try {
+ currentlyRendering.incrementAndGet();
+ Path path = renderFrames(ponder);
+
+ CreatePonderWonder.chat("Finished rendering Ponder: " + path);
+ CreatePonderWonder.LOGGER.info("Finished rendering Ponder: {}", path);
+
+ } catch (IOException | InterruptedException | ExecutionException e) {
+ CreatePonderWonder.chat("Error: " + e.getMessage());
+ CreatePonderWonder.LOGGER.error("Could not save image", e);
+ } finally {
+ currentlyRendering.decrementAndGet();
+ renderDone.signal();
+ lock.unlock();
+ }
+ }
+
+ private Path renderFrames(PonderWonderUI ponder) throws InterruptedException, ExecutionException, IOException {
+ Path path = Paths.get(
+ basePath,
+ CreatePonderWonder.MODID,
+ ponder.getActiveScene().getString("out"));
+ Files.createDirectories(path);
+
+ for (int frame = 0; frame < MAX_FRAMES; frame++) {
+ Promise<NativeImage> promise = renderFrame(ponder, frame);
+ NativeImage img = promise.get();
+
+ Path out = path.resolve(String.format("%06d.png", frame));
+ img.write(out);
+
+ if (frame % 3 == 2) {
+ ponder.tick();
+ }
+ }
+
+ return path;
+ }
+
+ public Promise<NativeImage> renderFrame(PonderWonderUI ponder, int frame) {
+ Promise<NativeImage> promise = GlobalEventExecutor.INSTANCE.newPromise();
+
+ float pt = (frame % FPS) / (FPS / 3.0f);
+ Minecraft.getInstance().field_213275_aU.add(() -> {
+ try {
+ NativeImage img = RenderUtils.render(ms ->
+ ponder.ponderWonderRenderWindow(ms, pt));
+ promise.setSuccess(img);
+ } catch (Exception e) {
+ promise.setFailure(e);
+ }
+ });
+
+ return promise;
+ }
+
+ private void enqueueAllPonders() {
+ allEnqueued = false;
+ Iterable<PonderWonderUI> ponders = getPonders();
+
+ try {
+ for (PonderWonderUI ponder : ponders) {
+ pondersToRender.put(ponder);
+ }
+ allEnqueued = true;
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private Iterable<PonderWonderUI> getPonders() {
+ return PonderRegistry.all
+ .values()
+ .stream()
+ .map(PonderRegistry::compile)
+ .flatMap(List::stream)
+ .map(PonderWonderUI::new)
+ ::iterator;
+ }
+}+
\ No newline at end of file
diff --git a/src/main/java/de/oshgnacknak/create_ponder_wonder/commands/StartRenderingCommand.java b/src/main/java/de/oshgnacknak/create_ponder_wonder/commands/StartRenderingCommand.java
@@ -0,0 +1,18 @@
+package de.oshgnacknak.create_ponder_wonder.commands;
+
+import com.mojang.brigadier.Command;
+import com.mojang.brigadier.arguments.StringArgumentType;
+import com.mojang.brigadier.context.CommandContext;
+import com.mojang.brigadier.exceptions.CommandSyntaxException;
+import de.oshgnacknak.create_ponder_wonder.CreatePonderWonder;
+import net.minecraft.command.CommandSource;
+
+public class StartRenderingCommand implements Command<CommandSource> {
+
+ @Override
+ public int run(CommandContext<CommandSource> context) throws CommandSyntaxException {
+ String path = StringArgumentType.getString(context, "path");
+ CreatePonderWonder.PONDER_RENDERER.start(path);
+ return SINGLE_SUCCESS;
+ }
+}
diff --git a/src/main/java/de/oshgnacknak/create_ponder_wonder/commands/StopRenderingCommand.java b/src/main/java/de/oshgnacknak/create_ponder_wonder/commands/StopRenderingCommand.java
@@ -0,0 +1,15 @@
+package de.oshgnacknak.create_ponder_wonder.commands;
+
+import com.mojang.brigadier.context.CommandContext;
+import com.mojang.brigadier.exceptions.CommandSyntaxException;
+import de.oshgnacknak.create_ponder_wonder.CreatePonderWonder;
+import net.minecraft.command.CommandSource;
+
+public class StopRenderingCommand implements com.mojang.brigadier.Command<net.minecraft.command.CommandSource> {
+
+ @Override
+ public int run(CommandContext<CommandSource> context) throws CommandSyntaxException {
+ CreatePonderWonder.PONDER_RENDERER.stop();
+ return SINGLE_SUCCESS;
+ }
+}