Skip to content

Commit

Permalink
Use vanilla thread interruption for controlling engine systems
Browse files Browse the repository at this point in the history
  • Loading branch information
ultraq committed Jan 30, 2024
1 parent 5f8a90e commit fd2baaa
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 97 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package nz.net.ultraq.redhorizon.engine
import org.slf4j.Logger
import org.slf4j.LoggerFactory

import java.util.concurrent.CancellationException
import java.util.concurrent.CountDownLatch
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
Expand Down Expand Up @@ -66,9 +67,9 @@ class Engine {
}

/**
* Start the game engine. This will assign all entity systems their own
* thread to operate on the scene. This method will block until all systems
* have signalled their ready status.
* Start the game engine. This will assign all systems their own thread to
* run. This method will block until all systems have signalled their ready
* status.
*/
void start() {

Expand Down Expand Up @@ -109,7 +110,7 @@ class Engine {

enginesStoppingSemaphore.tryAcquireAndRelease { ->
if (!engineStopped) {
systems*.stop()
systemTasks*.cancel(true)
engineStopped = true
logger.debug('Engine stopped')
}
Expand All @@ -121,7 +122,14 @@ class Engine {
*/
void waitUntilStopped() {

systemTasks*.get()
systemTasks.each { systemTask ->
try {
systemTask.get()
}
catch (CancellationException ignored) {
// Do nothing
}
}
logger.debug('All systems stopped')
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
/*
/*
* Copyright 2023, 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 @@ -16,21 +16,22 @@

package nz.net.ultraq.redhorizon.engine

import nz.net.ultraq.redhorizon.async.RunnableWorker
import nz.net.ultraq.redhorizon.engine.scenegraph.Scene
import nz.net.ultraq.redhorizon.events.EventTarget

import groovy.transform.TupleConstructor

/**
* A system provides behaviour for a component or set of components. Systems
* traverse a {@link Scene}, looking for the components they work with, and then
* doing something with the data in those components.
*
* @author Emanuel Rabina
*/
@TupleConstructor(defaults = false)
abstract class EngineSystem implements EventTarget, RunnableWorker {
abstract class EngineSystem implements Runnable, EventTarget {

final Scene scene

protected EngineSystem(Scene scene) {

this.scene = scene
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

package nz.net.ultraq.redhorizon.engine.audio

import nz.net.ultraq.redhorizon.async.RateLimitedLoop
import nz.net.ultraq.redhorizon.engine.EngineSystem
import nz.net.ultraq.redhorizon.engine.SystemReadyEvent
import nz.net.ultraq.redhorizon.engine.SystemStoppedEvent
Expand Down Expand Up @@ -48,9 +47,6 @@ class AudioSystem extends EngineSystem {
private final CopyOnWriteArrayList<SceneElement> addedElements = new CopyOnWriteArrayList<>()
private final CopyOnWriteArrayList<SceneElement> removedElements = new CopyOnWriteArrayList<>()

@Delegate
private RateLimitedLoop systemLoop

/**
* Constructor, build a new engine for rendering audio.
*
Expand Down Expand Up @@ -89,40 +85,46 @@ class AudioSystem extends EngineSystem {

// Rendering loop
logger.debug('Audio system in render loop...')
systemLoop = new RateLimitedLoop(10, { ->

// Initialize or delete objects which have been added/removed to/from the scene
if (addedElements) {
def elementsToInit = new ArrayList<SceneElement>(addedElements)
elementsToInit.each { elementToInit ->
elementToInit.accept { element ->
if (element instanceof AudioElement) {
element.init(renderer)
while (!Thread.interrupted()) {
try {

// Initialize or delete objects which have been added/removed to/from the scene
if (addedElements) {
def elementsToInit = new ArrayList<SceneElement>(addedElements)
elementsToInit.each { elementToInit ->
elementToInit.accept { element ->
if (element instanceof AudioElement) {
element.init(renderer)
}
}
}
addedElements.removeAll(elementsToInit)
}
addedElements.removeAll(elementsToInit)
}
if (removedElements) {
def elementsToDelete = new ArrayList<SceneElement>(removedElements)
elementsToDelete.each { elementToInit ->
elementToInit.accept { element ->
if (element instanceof AudioElement) {
element.delete(renderer)
if (removedElements) {
def elementsToDelete = new ArrayList<SceneElement>(removedElements)
elementsToDelete.each { elementToInit ->
elementToInit.accept { element ->
if (element instanceof AudioElement) {
element.delete(renderer)
}
}
}
removedElements.removeAll(elementsToDelete)
}
removedElements.removeAll(elementsToDelete)
}

// Run the audio elements
scene.accept { element ->
if (element instanceof AudioElement) {
element.render(renderer)
// Run the audio elements
scene.accept { element ->
if (element instanceof AudioElement) {
element.render(renderer)
}
}

Thread.sleep(10)
}
catch (InterruptedException ignored) {
break
}
})
systemLoop.run()
}

// Shutdown
scene.accept { sceneElement ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

package nz.net.ultraq.redhorizon.engine.graphics

import nz.net.ultraq.redhorizon.async.ControlledLoop
import nz.net.ultraq.redhorizon.engine.EngineSystem
import nz.net.ultraq.redhorizon.engine.SystemReadyEvent
import nz.net.ultraq.redhorizon.engine.SystemStoppedEvent
Expand All @@ -29,7 +28,6 @@ import nz.net.ultraq.redhorizon.engine.input.InputEventStream
import nz.net.ultraq.redhorizon.engine.input.KeyEvent
import nz.net.ultraq.redhorizon.engine.input.MouseButtonEvent
import nz.net.ultraq.redhorizon.engine.scenegraph.Scene
import nz.net.ultraq.redhorizon.events.EventTarget

import org.slf4j.Logger
import org.slf4j.LoggerFactory
Expand All @@ -41,7 +39,7 @@ import static org.lwjgl.glfw.GLFW.*
*
* @author Emanuel Rabina
*/
class GraphicsSystem extends EngineSystem implements EventTarget {
class GraphicsSystem extends EngineSystem {

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

Expand All @@ -57,9 +55,6 @@ class GraphicsSystem extends EngineSystem implements EventTarget {
private boolean shouldToggleVsync
private long lastClickTime

@Delegate
private ControlledLoop systemLoop

/**
* Constructor, build a new system for rendering graphics.
*
Expand Down Expand Up @@ -172,20 +167,24 @@ class GraphicsSystem extends EngineSystem implements EventTarget {

// Rendering loop
logger.debug('Graphics system in render loop...')
systemLoop = new ControlledLoop({ !window.shouldClose() }, { ->
if (shouldToggleFullScreen) {
window.toggleFullScreen()
shouldToggleFullScreen = false
while (!window.shouldClose() && !Thread.interrupted()) {
try {
if (shouldToggleFullScreen) {
window.toggleFullScreen()
shouldToggleFullScreen = false
}
if (shouldToggleVsync) {
window.toggleVsync()
shouldToggleVsync = false
}
pipeline.render()
window.swapBuffers()
window.pollEvents()
}
if (shouldToggleVsync) {
window.toggleVsync()
shouldToggleVsync = false
catch (InterruptedException ignored) {
break
}
pipeline.render()
window.swapBuffers()
window.pollEvents()
})
systemLoop.run()
}

// Shutdown
logger.debug('Shutting down graphics system')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,41 +16,29 @@

package nz.net.ultraq.redhorizon.engine.time

import nz.net.ultraq.redhorizon.async.ControlledLoop
import nz.net.ultraq.redhorizon.engine.EngineSystem
import nz.net.ultraq.redhorizon.engine.SystemReadyEvent
import nz.net.ultraq.redhorizon.engine.SystemStoppedEvent
import nz.net.ultraq.redhorizon.engine.scenegraph.Scene

import org.slf4j.Logger
import org.slf4j.LoggerFactory

import groovy.transform.InheritConstructors

/**
* A separate time source from the usual system time, allowing game time to flow
* at different speeds.
*
* @author Emanuel Rabina
*/
@InheritConstructors
class GameClock extends EngineSystem {

private static Logger logger = LoggerFactory.getLogger(GameClock)

private float speed = 1.0f
private float lastSpeed

@Delegate
private ControlledLoop timeLoop

/**
* Constructor, creates a new time system over a scene.
*
* @param scene
*/
GameClock(Scene scene) {

super(scene)
}

/**
* Return whether or not time has been paused.
*
Expand Down Expand Up @@ -95,30 +83,34 @@ class GameClock extends EngineSystem {
long currentTimeMillis = lastSystemTimeMillis

logger.debug('Game clock in update loop')
timeLoop = new ControlledLoop({ ->
Thread.sleep(10)
var currentSystemTimeMillis = System.currentTimeMillis()
var delta = currentSystemTimeMillis - lastSystemTimeMillis

// Normal flow of time, accumulate ticks at the same rate as system time
if (speed == 1.0f) {
currentTimeMillis += delta
}
// Modified flow, accumulate ticks at system time * flow speed
else {
currentTimeMillis += (delta * speed)
}
while (!Thread.interrupted()) {
try {
Thread.sleep(10)
var currentSystemTimeMillis = System.currentTimeMillis()
var delta = currentSystemTimeMillis - lastSystemTimeMillis

// Normal flow of time, accumulate ticks at the same rate as system time
if (speed == 1.0f) {
currentTimeMillis += delta
}
// Modified flow, accumulate ticks at system time * flow speed
else {
currentTimeMillis += (delta * speed)
}

// Update time with scene objects
scene.accept { element ->
if (element instanceof Temporal) {
element.tick(currentTimeMillis)
// Update time with scene objects
scene.accept { element ->
if (element instanceof Temporal) {
element.tick(currentTimeMillis)
}
}
}

lastSystemTimeMillis = currentSystemTimeMillis
})
timeLoop.run()
lastSystemTimeMillis = currentSystemTimeMillis
}
catch (InterruptedException ignored) {
break
}
}

trigger(new SystemStoppedEvent())
logger.debug('Game clock stopped')
Expand Down

0 comments on commit fd2baaa

Please sign in to comment.