Skip to content

Commit

Permalink
Convert Explorer to use new application builder-like API
Browse files Browse the repository at this point in the history
  • Loading branch information
ultraq committed Feb 20, 2024
1 parent 997c450 commit c64b9ec
Show file tree
Hide file tree
Showing 13 changed files with 176 additions and 159 deletions.
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
/*
/*
* Copyright 2022, Emanuel Rabina (http://www.ultraq.net.nz/)
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
Expand All @@ -27,7 +27,7 @@ import java.util.concurrent.Callable

/**
* CLI wrapper for launching the Red Horizon Explorer 🔎
*
*
* @author Emanuel Rabina
*/
@Command(name = 'explorer')
Expand All @@ -42,7 +42,7 @@ class ExplorerCli implements Callable<Integer> {
@Override
Integer call() {

new Explorer(commandSpec.parent().version()[0], paletteOptions.loadPalette()).start()
new Explorer(commandSpec.parent().version()[0], paletteOptions.loadPalette())
return 0
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ class MediaPlayer {
private static final Logger logger = LoggerFactory.getLogger(MediaPlayer)

private ResourceFile mediaFile
private Scene scene
private Node mediaNode

/**
Expand All @@ -62,13 +61,11 @@ class MediaPlayer {
MediaPlayer(ResourceFile resourceFile, AudioConfiguration audioConfig, GraphicsConfiguration graphicsConfig) {

mediaFile = resourceFile
scene = new Scene()

new Application('Media Player')
.addAudioSystem(audioConfig)
.addGraphicsSystem(graphicsConfig)
.addTimeSystem()
.useScene(scene)
.onApplicationStart(this::onApplicationStart)
.onApplicationStop(this::onApplicationStop)
.start()
Expand All @@ -78,7 +75,7 @@ class MediaPlayer {
* Create the appropriate media node for the media file, adding it to the
* scene.
*/
private void onApplicationStart(Application application) {
private void onApplicationStart(Application application, Scene scene) {

logger.info('File details: {}', mediaFile)

Expand All @@ -101,7 +98,7 @@ class MediaPlayer {
/**
* Remove the media node on cleanup.
*/
private void onApplicationStop() {
private void onApplicationStop(Application application, Scene scene) {

scene.removeNode(mediaNode)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,14 @@ class MediaPlayerCli implements Callable<Integer> {
Thread.currentThread().name = 'Media Player [main]'
logger.info('Red Horizon Media Player {}', commandSpec.parent().version()[0])

def audioConfig = audioOptions.asAudioConfiguration()
def graphicsConfig = graphicsOptions.asGraphicsConfiguration(
renderResolution: new Dimension(1280, 800)
)

fileOptions.useFile(logger) { resourceFile ->
new MediaPlayer(resourceFile, audioConfig, graphicsConfig)
new MediaPlayer(
resourceFile,
audioOptions.asAudioConfiguration(),
graphicsOptions.asGraphicsConfiguration(
renderResolution: new Dimension(1280, 800)
)
)
}

return 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,15 @@ import nz.net.ultraq.redhorizon.engine.audio.AudioSystem
import nz.net.ultraq.redhorizon.engine.graphics.GraphicsConfiguration
import nz.net.ultraq.redhorizon.engine.graphics.GraphicsSystem
import nz.net.ultraq.redhorizon.engine.graphics.WindowCreatedEvent
import nz.net.ultraq.redhorizon.engine.graphics.WindowMaximizedEvent
import nz.net.ultraq.redhorizon.engine.graphics.imgui.DebugOverlayRenderPass
import nz.net.ultraq.redhorizon.engine.graphics.imgui.GuiEvent
import nz.net.ultraq.redhorizon.engine.graphics.pipeline.OverlayRenderPass
import nz.net.ultraq.redhorizon.engine.input.InputEventStream
import nz.net.ultraq.redhorizon.engine.input.KeyEvent
import nz.net.ultraq.redhorizon.engine.scenegraph.Scene
import nz.net.ultraq.redhorizon.engine.time.TimeSystem
import nz.net.ultraq.redhorizon.events.EventTarget
import static nz.net.ultraq.redhorizon.engine.graphics.imgui.GuiEvent.EVENT_TYPE_STOP

import org.slf4j.Logger
Expand All @@ -35,7 +38,6 @@ import static org.lwjgl.glfw.GLFW.*

import groovy.transform.TupleConstructor
import java.util.concurrent.Semaphore
import java.util.function.Function

/**
* A base for developing an application that uses the Red Horizon engine and
Expand All @@ -56,7 +58,7 @@ import java.util.function.Function
* @author Emanuel Rabina
*/
@TupleConstructor(defaults = false)
class Application {
class Application implements EventTarget {

private static final Logger logger = LoggerFactory.getLogger(Application)

Expand All @@ -66,16 +68,16 @@ class Application {
private final InputEventStream inputEventStream = new InputEventStream()

private Scene scene
private Function<Application, Void> applicationStart
private Function<Application, Void> applicationStop
private ApplicationEventHandler applicationStart
private ApplicationEventHandler applicationStop
private boolean applicationStopped
private Semaphore applicationStoppingSemaphore = new Semaphore(1)

/**
* Add the audio system to this application. The audio system will run on its
* own thread.
*/
Application addAudioSystem(AudioConfiguration config) {
Application addAudioSystem(AudioConfiguration config = new AudioConfiguration()) {

engine << new AudioSystem(config)
return this
Expand All @@ -86,7 +88,8 @@ class Application {
* on its own thread, and the window it creates will be the source of user
* input into the application.
*/
Application addGraphicsSystem(GraphicsConfiguration config) {
Application addGraphicsSystem(GraphicsConfiguration config = new GraphicsConfiguration(),
OverlayRenderPass... overlayRenderPasses) {

var graphicsSystem = new GraphicsSystem(windowTitle, inputEventStream, config)
graphicsSystem.on(WindowCreatedEvent) { event ->
Expand All @@ -95,10 +98,16 @@ class Application {
graphicsSystem.on(SystemReadyEvent) { event ->
var audioSystem = engine.systems.find { it instanceof AudioSystem } as AudioSystem
graphicsSystem.renderPipeline.addOverlayPass(
new DebugOverlayRenderPass(audioSystem.renderer, graphicsSystem.renderer, config.debug)
new DebugOverlayRenderPass(config.debug)
.addAudioRenderer(audioSystem.renderer)
.addGraphicsRenderer(graphicsSystem.renderer)
.toggleWith(inputEventStream, GLFW_KEY_D)
)
overlayRenderPasses.each { overlayRenderPass ->
graphicsSystem.renderPipeline.addOverlayPass(overlayRenderPass)
}
}
graphicsSystem.relay(WindowMaximizedEvent, this)
engine << graphicsSystem
return this
}
Expand All @@ -109,13 +118,13 @@ class Application {
*/
Application addTimeSystem() {

var timeSystem = new TimeSystem()
engine << timeSystem
engine << new TimeSystem()
return this
}

/**
* Use this application with the given scene.
* Use this application with the given scene. If this method is never used,
* then an empty scene will be created during startup.
*/
Application useScene(Scene scene) {

Expand All @@ -130,6 +139,10 @@ class Application {

logger.debug('Initializing application...')

if (!scene) {
scene = new Scene()
}

// Universal quit on exit
inputEventStream.on(KeyEvent) { event ->
if (event.action == GLFW_PRESS && event.key == GLFW_KEY_ESCAPE) {
Expand All @@ -147,7 +160,7 @@ class Application {
engine.start()
engine.systems*.scene = scene
scene.inputEventStream = inputEventStream
applicationStart.apply(this)
applicationStart.apply(this, scene)

engine.waitUntilStopped()
}
Expand All @@ -160,7 +173,7 @@ class Application {
applicationStoppingSemaphore.tryAcquireAndRelease { ->
if (!applicationStopped) {
logger.debug('Stopping application...')
applicationStop.apply(this)
applicationStop.apply(this, scene)
engine.stop()
applicationStopped = true
}
Expand All @@ -170,7 +183,7 @@ class Application {
/**
* Configure some code to run after engine startup.
*/
Application onApplicationStart(Function<Application, Void> handler) {
Application onApplicationStart(ApplicationEventHandler handler) {

applicationStart = handler
return this
Expand All @@ -179,9 +192,20 @@ class Application {
/**
* Configure some code to run before engine shutdown.
*/
Application onApplicationStop(Function<Application, Void> handler) {
Application onApplicationStop(ApplicationEventHandler handler) {

applicationStop = handler
return this
}

/**
* The functional interface called for each application lifecycle event. It
* is provided the application itself, plus either the configured or default
* scene, whichever is current at that point in time.
*/
@FunctionalInterface
static interface ApplicationEventHandler {

void apply(Application application, Scene scene);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ class AudioSystem extends EngineSystem implements AudioRequests {
*/
AudioSystem(AudioConfiguration config) {

this.config = config ?: new AudioConfiguration()
this.config = config
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ class GraphicsSystem extends EngineSystem implements GraphicsRequests {
private static final Logger logger = LoggerFactory.getLogger(GraphicsSystem)

private final String windowTitle
private final GraphicsConfiguration config
private final InputEventStream inputEventStream
private final GraphicsConfiguration config
private final BlockingQueue<Tuple2<Request, CompletableFuture<GraphicsResource>>> creationRequests = new LinkedBlockingQueue<>()
private final BlockingQueue<GraphicsResource> deletionRequests = new LinkedBlockingQueue<>()

Expand All @@ -69,7 +69,7 @@ class GraphicsSystem extends EngineSystem implements GraphicsRequests {

this.windowTitle = windowTitle
this.inputEventStream = inputEventStream
this.config = config ?: new GraphicsConfiguration()
this.config = config
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,30 @@ class DebugOverlayRenderPass implements OverlayRenderPass {
private int debugWindowSizeY = 200

/**
* Constructor, tie the debug overlay to the renderer so it can get the
* information it needs to build the display.
*
* @param audioRenderer
* @param graphicsRenderer
* @param enabled
* Constructor, create a new blank overlay. This is made more useful by
* adding the renderers via the {@code add*} methods to get stats on their
* use.
*/
DebugOverlayRenderPass(AudioRenderer audioRenderer, GraphicsRenderer graphicsRenderer, boolean enabled) {
DebugOverlayRenderPass(boolean enabled) {

ImGuiLoggingAppender.instance.on(ImGuiLogEvent) { event ->
if (event.persistentKey) {
persistentLines[event.persistentKey] = event.message
}
else {
while (!debugLines.offer(event.message)) {
debugLines.poll()
}
}
}

this.enabled = enabled
}

/**
* Add the audio renderer to get stats on audio sources, buffers, etc.
*/
DebugOverlayRenderPass addAudioRenderer(AudioRenderer audioRenderer) {

audioRenderer.on(AudioRendererEvent) { event ->
switch (event) {
Expand All @@ -81,6 +97,13 @@ class DebugOverlayRenderPass implements OverlayRenderPass {
case SourceDeletedEvent -> activeSources.decrementAndGet()
}
}
return this
}

/**
* Add the graphics renderer to get status on draws, textures, etc.
*/
DebugOverlayRenderPass addGraphicsRenderer(GraphicsRenderer graphicsRenderer) {

graphicsRenderer.on(GraphicsRendererEvent) { event ->
switch (event) {
Expand All @@ -93,19 +116,7 @@ class DebugOverlayRenderPass implements OverlayRenderPass {
case TextureDeletedEvent -> activeTextures.decrementAndGet()
}
}

ImGuiLoggingAppender.instance.on(ImGuiLogEvent) { event ->
if (event.persistentKey) {
persistentLines[event.persistentKey] = event.message
}
else {
while (!debugLines.offer(event.message)) {
debugLines.poll()
}
}
}

this.enabled = enabled
return this
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
/*
/*
* Copyright 2021, Emanuel Rabina (http://www.ultraq.net.nz/)
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
Expand Down Expand Up @@ -44,8 +44,8 @@ class InputEventStream implements EventTarget {
var deregisterEventFunction = on(control.event, control)
trigger(new ControlAddedEvent(control))

return { unused ->
deregisterEventFunction.apply(null)
return { ->
deregisterEventFunction.deregister()
trigger(new ControlRemovedEvent(control))
}
}
Expand Down
Loading

0 comments on commit c64b9ec

Please sign in to comment.