From 1b099f9ef66b92ec6214f7a6942813ab579c4574 Mon Sep 17 00:00:00 2001 From: johni0702 Date: Sat, 31 Dec 2016 16:29:17 +0100 Subject: [PATCH] Add render queue --- .../com/replaymod/render/ReplayModRender.java | 22 +- .../replaymod/render/gui/GuiRenderQueue.java | 277 ++++++++++++++++++ .../render/gui/GuiRenderSettings.java | 14 +- .../render/rendering/VideoRenderer.java | 7 +- .../com/replaymod/render/utils/RenderJob.java | 12 + .../simplepathing/ReplayModSimplePathing.java | 4 +- .../simplepathing/gui/GuiPathing.java | 20 +- .../assets/replaymod/lang/en_US.lang | 6 + 8 files changed, 354 insertions(+), 8 deletions(-) create mode 100644 src/main/java/com/replaymod/render/gui/GuiRenderQueue.java create mode 100644 src/main/java/com/replaymod/render/utils/RenderJob.java diff --git a/src/main/java/com/replaymod/render/ReplayModRender.java b/src/main/java/com/replaymod/render/ReplayModRender.java index 9dc9781a..0ce8f7a3 100644 --- a/src/main/java/com/replaymod/render/ReplayModRender.java +++ b/src/main/java/com/replaymod/render/ReplayModRender.java @@ -1,16 +1,22 @@ package com.replaymod.render; import com.replaymod.core.ReplayMod; +import com.replaymod.render.utils.RenderJob; +import com.replaymod.replay.events.ReplayCloseEvent; import net.minecraft.crash.CrashReport; import net.minecraft.util.ReportedException; import net.minecraftforge.common.config.Configuration; +import net.minecraftforge.fml.common.FMLCommonHandler; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import org.apache.commons.io.FileUtils; import org.apache.logging.log4j.Logger; import java.io.File; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; @Mod(modid = ReplayModRender.MOD_ID, version = "@MOD_VERSION@", @@ -24,9 +30,10 @@ public class ReplayModRender { private ReplayMod core; - private Logger logger; + public static Logger LOGGER; private Configuration configuration; + private final List renderQueue = new ArrayList<>(); public ReplayMod getCore() { return core; @@ -34,13 +41,20 @@ public class ReplayModRender { @Mod.EventHandler public void preInit(FMLPreInitializationEvent event) { - logger = event.getModLog(); + LOGGER = event.getModLog(); core = ReplayMod.instance; configuration = new Configuration(event.getSuggestedConfigurationFile()); + FMLCommonHandler.instance().bus().register(this); + core.getSettingsRegistry().register(Setting.class); } + @SubscribeEvent + public void onReplayClose(ReplayCloseEvent.Post event) { + renderQueue.clear(); + } + public File getVideoFolder() { String path = core.getSettingsRegistry().get(Setting.RENDER_PATH); File folder = new File(path.startsWith("./") ? core.getMinecraft().mcDataDir : null, path); @@ -55,4 +69,8 @@ public class ReplayModRender { public Configuration getConfiguration() { return configuration; } + + public List getRenderQueue() { + return renderQueue; + } } diff --git a/src/main/java/com/replaymod/render/gui/GuiRenderQueue.java b/src/main/java/com/replaymod/render/gui/GuiRenderQueue.java new file mode 100644 index 00000000..9a96835b --- /dev/null +++ b/src/main/java/com/replaymod/render/gui/GuiRenderQueue.java @@ -0,0 +1,277 @@ +package com.replaymod.render.gui; + +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.replaymod.render.ReplayModRender; +import com.replaymod.render.rendering.VideoRenderer; +import com.replaymod.render.utils.RenderJob; +import com.replaymod.replay.ReplayHandler; +import com.replaymod.replaystudio.pathing.path.Timeline; +import com.replaymod.replaystudio.util.I18n; +import de.johni0702.minecraft.gui.GuiRenderer; +import de.johni0702.minecraft.gui.RenderInfo; +import de.johni0702.minecraft.gui.container.AbstractGuiClickableContainer; +import de.johni0702.minecraft.gui.container.GuiContainer; +import de.johni0702.minecraft.gui.container.GuiPanel; +import de.johni0702.minecraft.gui.container.GuiVerticalList; +import de.johni0702.minecraft.gui.element.GuiButton; +import de.johni0702.minecraft.gui.element.GuiLabel; +import de.johni0702.minecraft.gui.element.GuiTextField; +import de.johni0702.minecraft.gui.layout.CustomLayout; +import de.johni0702.minecraft.gui.layout.GridLayout; +import de.johni0702.minecraft.gui.layout.HorizontalLayout; +import de.johni0702.minecraft.gui.popup.AbstractGuiPopup; +import de.johni0702.minecraft.gui.popup.GuiYesNoPopup; +import de.johni0702.minecraft.gui.utils.Colors; +import net.minecraft.client.gui.GuiErrorScreen; +import net.minecraft.crash.CrashReport; +import net.minecraft.util.ReportedException; +import org.lwjgl.util.Dimension; +import org.lwjgl.util.ReadableDimension; + +import javax.annotation.Nullable; +import java.io.IOException; +import java.util.List; + +import static com.replaymod.render.ReplayModRender.LOGGER; + +public class GuiRenderQueue extends AbstractGuiPopup { + private final GuiLabel title = new GuiLabel().setI18nText("replaymod.gui.renderqueue.title").setColor(Colors.BLACK); + private final GuiVerticalList list = new GuiVerticalList().setDrawShadow(true).setDrawSlider(true); + private final GuiButton addButton = new GuiButton().setI18nLabel("replaymod.gui.renderqueue.add").setSize(150, 20); + private final GuiButton renameButton = new GuiButton().setI18nLabel("replaymod.gui.rename").setSize(73, 20); + private final GuiButton removeButton = new GuiButton().setI18nLabel("replaymod.gui.remove").setSize(73, 20); + private final GuiButton renderButton = new GuiButton().setI18nLabel("replaymod.gui.render").setSize(150, 20); + private final GuiButton closeButton = new GuiButton().setI18nLabel("replaymod.gui.close").setSize(150, 20).onClick(this::close); + + /* + + |---------------------------------| + | Add | Render | + |---------------------------------| + | Rename | Remove | Close | + |---------------------------------| + + */ + private final GuiPanel buttonPanel = new GuiPanel() + .setLayout(new GridLayout().setSpacingX(5).setSpacingY(5).setColumns(2)) + .addElements(null, + addButton, + renderButton, + new GuiPanel().setLayout(new HorizontalLayout().setSpacing(4)).addElements(null, + renameButton, removeButton), + closeButton); + + private final GuiContainer container; + private Entry selectedEntry; + + { + popup.setLayout(new CustomLayout() { + @Override + protected void layout(GuiPanel container, int width, int height) { + pos(title, width / 2 - width(title) / 2, 0); + pos(list, 0, y(title) + height(title) + 5); + pos(buttonPanel, width / 2 - width(buttonPanel) / 2, height - height(buttonPanel)); + size(list, width, y(buttonPanel) - y(list) - 10); + } + + @Override + public ReadableDimension calcMinSize(GuiContainer container) { + ReadableDimension screenSize = GuiRenderQueue.this.container.getMinSize(); + return new Dimension(screenSize.getWidth() - 40, + screenSize.getHeight() - 20 - buttonPanel.getMinSize().getHeight() - title.getMinSize().getHeight()); + } + }).addElements(null, title, list, buttonPanel); + } + + public GuiRenderQueue(GuiContainer container, GuiRenderSettings guiRenderSettings, ReplayHandler replayHandler, Timeline timeline) { + super(container); + this.container = container; + LOGGER.trace("Opening render queue popup"); + + setBackgroundColor(Colors.DARK_TRANSPARENT); + + List queue = ReplayModRender.instance.getRenderQueue(); + + for (RenderJob renderJob : queue) { + LOGGER.trace("Adding {} to job queue list", renderJob); + list.getListPanel().addElements(null, new Entry(renderJob)); + } + + addButton.onClick(() -> { + LOGGER.trace("Add button clicked"); + // Open popup + GuiYesNoPopup popup = GuiYesNoPopup.open(container) + .setYesI18nLabel("replaymod.gui.add").setNoI18nLabel("replaymod.gui.cancel"); + popup.getInfo().setLayout(new HorizontalLayout(HorizontalLayout.Alignment.CENTER).setSpacing(5)); + // Add content + GuiLabel label = new GuiLabel().setI18nText("replaymod.gui.renderqueue.jobname").setColor(Colors.BLACK); + GuiTextField nameField = new GuiTextField().setSize(150, 20).setFocused(true); + popup.getInfo().addElements(new HorizontalLayout.Data(0.5), label, nameField); + // Disable "Yes" button while name is empty + nameField.onTextChanged(old -> popup.getYesButton().setEnabled(!nameField.getText().isEmpty())).onEnter(() -> { + if (popup.getYesButton().isEnabled()) { + popup.getYesButton().onClick(); + } + }); + popup.getYesButton().setDisabled(); + // Register callback + Futures.addCallback(popup.getFuture(), new FutureCallback() { + @Override + public void onSuccess(@Nullable Boolean result) { + if (result == Boolean.TRUE) { + RenderJob newJob = new RenderJob(); + newJob.setName(nameField.getText()); + newJob.setSettings(guiRenderSettings.save(false)); + newJob.setTimeline(timeline); + LOGGER.trace("Adding new job: {}", newJob); + queue.add(newJob); + list.getListPanel().addElements(null, new Entry(newJob)); + } else { + LOGGER.trace("Adding cancelled"); + } + } + + @Override + public void onFailure(Throwable t) { + LOGGER.error("Add Job popup:", t); + } + }); + }); + if (guiRenderSettings != null) { + addButton.setEnabled(guiRenderSettings.renderButton.isEnabled()); + } + + renameButton.onClick(() -> { + LOGGER.trace("Rename button clicked for {}", selectedEntry.job); + // Open popup + GuiYesNoPopup popup = GuiYesNoPopup.open(container) + .setYesI18nLabel("replaymod.gui.rename").setNoI18nLabel("replaymod.gui.cancel"); + popup.getInfo().setLayout(new HorizontalLayout(HorizontalLayout.Alignment.CENTER).setSpacing(5)); + // Add content + GuiLabel label = new GuiLabel().setI18nText("replaymod.gui.renderqueue.jobname").setColor(Colors.BLACK); + GuiTextField nameField = new GuiTextField().setSize(150, 20).setFocused(true) + .setText(selectedEntry.job.getName()); + popup.getInfo().addElements(new HorizontalLayout.Data(0.5), label, nameField); + // Disable "Yes" button while name is empty + nameField.onTextChanged(old -> popup.getYesButton().setEnabled(!nameField.getText().isEmpty())).onEnter(() -> { + if (popup.getYesButton().isEnabled()) { + popup.getYesButton().onClick(); + } + }); + // Register callback + Futures.addCallback(popup.getFuture(), new FutureCallback() { + @Override + public void onSuccess(@Nullable Boolean result) { + if (result == Boolean.TRUE) { + LOGGER.trace("Renaming {} to \"{}\"", selectedEntry.job, nameField.getText()); + selectedEntry.setName(nameField.getText()); + } else { + LOGGER.trace("Renaming cancelled"); + } + } + + @Override + public void onFailure(Throwable t) { + LOGGER.error("Rename Job popup:", t); + } + }); + }); + + removeButton.onClick(() -> { + LOGGER.trace("Remove button clicked for {}", selectedEntry.job); + list.getListPanel().removeElement(selectedEntry); + queue.remove(selectedEntry.job); + selectedEntry = null; + updateButtons(); + }); + + renderButton.onClick(() -> { + LOGGER.trace("Render button clicked"); + // Close all GUIs (so settings in GuiRenderSettings are saved) + getMinecraft().displayGuiScreen(null); + // Start rendering + for (RenderJob renderJob : queue) { + LOGGER.info("Starting render job {}", renderJob); + try { + VideoRenderer videoRenderer = new VideoRenderer(renderJob.getSettings(), replayHandler, renderJob.getTimeline()); + videoRenderer.renderVideo(); + } catch (IOException e) { + LOGGER.error("Rendering video:", e); + GuiErrorScreen errorScreen = new GuiErrorScreen(I18n.format("replaymod.gui.rendering.error.title"), + I18n.format("replaymod.gui.rendering.error.message")); + getMinecraft().displayGuiScreen(errorScreen); + return; + } catch (Throwable t) { + CrashReport crashReport = CrashReport.makeCrashReport(t, "Rendering video"); + throw new ReportedException(crashReport); + } + } + }); + + updateButtons(); + } + + @Override + public void open() { + super.open(); + } + + @Override + protected GuiRenderQueue getThis() { + return this; + } + + public void updateButtons() { + renameButton.setEnabled(selectedEntry != null); + removeButton.setEnabled(selectedEntry != null); + renderButton.setEnabled(!list.getListPanel().getChildren().isEmpty()); + } + + public class Entry extends AbstractGuiClickableContainer { + public final GuiLabel label = new GuiLabel(this); + public final RenderJob job; + + public Entry(RenderJob job) { + this.job = job; + + setLayout(new CustomLayout() { + @Override + protected void layout(Entry container, int width, int height) { + pos(label, 5, height / 2 - height(label) / 2); + } + + @Override + public ReadableDimension calcMinSize(GuiContainer container) { + return new Dimension(buttonPanel.calcMinSize().getWidth(), 16); + } + }); + label.setText(job.getName()); + } + + @Override + protected void onClick() { + selectedEntry = this; + updateButtons(); + } + + @Override + public void draw(GuiRenderer renderer, ReadableDimension size, RenderInfo renderInfo) { + if (selectedEntry == this) { + renderer.drawRect(0, 0, size.getWidth(), size.getHeight(), Colors.BLACK); + renderer.drawRect(0, 0, 2, size.getHeight(), Colors.WHITE); + } + super.draw(renderer, size, renderInfo); + } + + public void setName(String name) { + job.setName(name); + label.setText(name); + } + + @Override + protected Entry getThis() { + return this; + } + } +} diff --git a/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java b/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java index a51189af..14cd84fc 100644 --- a/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java +++ b/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java @@ -185,6 +185,12 @@ public class GuiRenderSettings extends GuiScreen implements Closeable { public final GuiPanel buttonPanel = new GuiPanel(contentPanel).setLayout(new HorizontalLayout().setSpacing(4)); + public final GuiButton queueButton = new GuiButton(buttonPanel).onClick(new Runnable() { + @Override + public void run() { + new GuiRenderQueue(GuiRenderSettings.this, GuiRenderSettings.this, replayHandler, timeline).open(); + } + }).setSize(100, 20).setI18nLabel("replaymod.gui.renderqueue.open"); public final GuiButton renderButton = new GuiButton(buttonPanel).onClick(new Runnable() { @Override public void run() { @@ -204,13 +210,13 @@ public class GuiRenderSettings extends GuiScreen implements Closeable { throw new ReportedException(crashReport); } } - }).setSize(148, 20).setI18nLabel("replaymod.gui.render"); + }).setSize(100, 20).setI18nLabel("replaymod.gui.render"); public final GuiButton cancelButton = new GuiButton(buttonPanel).onClick(new Runnable() { @Override public void run() { getMinecraft().displayGuiScreen(null); } - }).setSize(148, 20).setI18nLabel("replaymod.gui.cancel"); + }).setSize(100, 20).setI18nLabel("replaymod.gui.cancel"); { setBackground(Background.NONE); @@ -437,4 +443,8 @@ public class GuiRenderSettings extends GuiScreen implements Closeable { return configuration.get("rendersettings", "settings", "{}", "Last state of the render settings GUI. Internal use only."); } + + public ReplayHandler getReplayHandler() { + return replayHandler; + } } diff --git a/src/main/java/com/replaymod/render/rendering/VideoRenderer.java b/src/main/java/com/replaymod/render/rendering/VideoRenderer.java index 8bc415cc..cf8ab6b2 100644 --- a/src/main/java/com/replaymod/render/rendering/VideoRenderer.java +++ b/src/main/java/com/replaymod/render/rendering/VideoRenderer.java @@ -2,6 +2,7 @@ package com.replaymod.render.rendering; import com.replaymod.core.ReplayMod; import com.replaymod.pathing.player.AbstractTimelinePlayer; +import com.replaymod.pathing.player.ReplayTimer; import com.replaymod.pathing.properties.TimestampProperty; import com.replaymod.render.RenderSettings; import com.replaymod.render.ReplayModRender; @@ -55,6 +56,7 @@ public class VideoRenderer implements RenderInfo { private boolean debugInfoWasShown; private Map originalSoundLevels; + private TimelinePlayer timelinePlayer; private Future timelinePlayerFuture; private ChunkLoadingRenderGlobal chunkLoadingRenderGlobal; @@ -174,7 +176,8 @@ public class VideoRenderer implements RenderInfo { } private void setup() { - timelinePlayerFuture = new TimelinePlayer(replayHandler).start(timeline); + timelinePlayer = new TimelinePlayer(replayHandler); + timelinePlayerFuture = timelinePlayer.start(timeline); if (!OpenGlHelper.isFramebufferEnabled()) { Display.setResizable(false); @@ -229,6 +232,8 @@ public class VideoRenderer implements RenderInfo { if (!timelinePlayerFuture.isDone()) { timelinePlayerFuture.cancel(false); } + // Tear down of the timeline player might only happen the next tick after it was cancelled + timelinePlayer.onTick(new ReplayTimer.UpdatedEvent()); if (!OpenGlHelper.isFramebufferEnabled()) { Display.setResizable(true); diff --git a/src/main/java/com/replaymod/render/utils/RenderJob.java b/src/main/java/com/replaymod/render/utils/RenderJob.java new file mode 100644 index 00000000..bae927d0 --- /dev/null +++ b/src/main/java/com/replaymod/render/utils/RenderJob.java @@ -0,0 +1,12 @@ +package com.replaymod.render.utils; + +import com.replaymod.render.RenderSettings; +import com.replaymod.replaystudio.pathing.path.Timeline; +import lombok.Data; + +@Data +public class RenderJob { + private String name; + private Timeline timeline; + private RenderSettings settings; +} diff --git a/src/main/java/com/replaymod/simplepathing/ReplayModSimplePathing.java b/src/main/java/com/replaymod/simplepathing/ReplayModSimplePathing.java index 8cae7eb1..8a884a04 100644 --- a/src/main/java/com/replaymod/simplepathing/ReplayModSimplePathing.java +++ b/src/main/java/com/replaymod/simplepathing/ReplayModSimplePathing.java @@ -35,13 +35,13 @@ public class ReplayModSimplePathing implements PathingRegistry { private ReplayMod core; - private Logger logger; + public static Logger LOGGER; private GuiPathing guiPathing; @Mod.EventHandler public void preInit(FMLPreInitializationEvent event) { - logger = event.getModLog(); + LOGGER = event.getModLog(); core = ReplayMod.instance; core.getSettingsRegistry().register(Setting.class); diff --git a/src/main/java/com/replaymod/simplepathing/gui/GuiPathing.java b/src/main/java/com/replaymod/simplepathing/gui/GuiPathing.java index 2c21a106..8eb27501 100644 --- a/src/main/java/com/replaymod/simplepathing/gui/GuiPathing.java +++ b/src/main/java/com/replaymod/simplepathing/gui/GuiPathing.java @@ -23,6 +23,7 @@ import com.replaymod.replaystudio.pathing.path.Path; import com.replaymod.replaystudio.pathing.path.PathSegment; import com.replaymod.replaystudio.pathing.path.Timeline; import com.replaymod.replaystudio.pathing.property.Property; +import com.replaymod.replaystudio.pathing.serialize.TimelineSerialization; import com.replaymod.replaystudio.util.EntityPositionTracker; import com.replaymod.replaystudio.util.Location; import com.replaymod.simplepathing.ReplayModSimplePathing; @@ -41,6 +42,7 @@ import de.johni0702.minecraft.gui.popup.AbstractGuiPopup; import de.johni0702.minecraft.gui.popup.GuiInfoPopup; import de.johni0702.minecraft.gui.popup.GuiYesNoPopup; import de.johni0702.minecraft.gui.utils.Colors; +import net.minecraft.crash.CrashReport; import net.minecraft.entity.Entity; import net.minecraftforge.fml.common.Loader; import org.apache.commons.lang3.tuple.Triple; @@ -56,10 +58,14 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.io.IOException; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.function.Consumer; +import static com.replaymod.core.utils.Utils.error; +import static com.replaymod.simplepathing.ReplayModSimplePathing.LOGGER; + /** * Gui plug-in to the GuiReplayOverlay for simple pathing. @@ -91,7 +97,19 @@ public class GuiPathing { @Override public void run() { if (!preparePathsForPlayback()) return; - new GuiRenderSettings(replayHandler, mod.getCurrentTimeline()).display(); + + // Clone the timeline passed to the settings gui as it may be stored for later rendering in a queue + Timeline timeline = mod.getCurrentTimeline(); + try { + TimelineSerialization serialization = new TimelineSerialization(mod, null); + String serialized = serialization.serialize(Collections.singletonMap("", timeline)); + timeline = serialization.deserialize(serialized).get(""); + } catch (Throwable t) { + error(LOGGER, replayHandler.getOverlay(), CrashReport.makeCrashReport(t, "Cloning timeline"), () -> {}); + return; + } + + new GuiRenderSettings(replayHandler, timeline).display(); } }).setSize(20, 20).setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE).setTexturePosH(40, 0) .setTooltip(new GuiTooltip().setI18nText("replaymod.gui.ingame.menu.renderpath")); diff --git a/src/main/resources/assets/replaymod/lang/en_US.lang b/src/main/resources/assets/replaymod/lang/en_US.lang index 8f1b8c44..3c85d8d8 100644 --- a/src/main/resources/assets/replaymod/lang/en_US.lang +++ b/src/main/resources/assets/replaymod/lang/en_US.lang @@ -432,6 +432,12 @@ replaymod.gui.rendersettings.antialiasing.x2=2x replaymod.gui.rendersettings.antialiasing.x4=4x replaymod.gui.rendersettings.antialiasing.x8=8x +#Render Queue GUI +replaymod.gui.renderqueue.title=Render Queue +replaymod.gui.renderqueue.open=Open Queue +replaymod.gui.renderqueue.jobname=Job Name: +replaymod.gui.renderqueue.add=Add current configuration + #Rendering GUI replaymod.gui.rendering.title=Rendering Video replaymod.gui.rendering.pause=Pause Rendering