Skip to content

Commit

Permalink
feat: This introduces background events queue in Escoria (#444)
Browse files Browse the repository at this point in the history
Co-authored-by: Dennis Ploeger <[email protected]>
  • Loading branch information
dploeger and dploeger authored Nov 22, 2021
1 parent f7c0bf2 commit 9adc7bb
Show file tree
Hide file tree
Showing 12 changed files with 319 additions and 112 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func run(command_params: Array) -> int:
var exited_previous_room = false

if command_params[1] \
and escoria.event_manager._running_event.name \
and escoria.event_manager.get_running_event("_front").name \
in ["exit_scene", "room_selector"]:
exited_previous_room = true
escoria.main.scene_transition.transition(
Expand Down Expand Up @@ -111,7 +111,7 @@ func run(command_params: Array) -> int:
var room_scene = res_room.instance()
if room_scene:
if command_params[1] \
and escoria.event_manager._running_event.name \
and escoria.event_manager.get_running_event("_front").name \
== "room_selector":
room_scene.enabled_automatic_transitions = true
else:
Expand Down
99 changes: 99 additions & 0 deletions addons/escoria-core/game/core-scripts/esc/commands/queue_event.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# `queue_event object event [channel] [block]`
#
# Queue another event to run
#
# **Parameters**
#
# - object: Object that holds the ESC script with the event
# - event: Name of the event to queue
# - channel: Channel to run the event on (default: `_front`)
# - block: Whether to wait for the queue to finish. This is only possible, if
# the queued event is not to be run on the same event as this command
# (default: `false`)
#
# @ESC
extends ESCBaseCommand
class_name QueueEventCommand


# Return the descriptor of the arguments of this command
func configure() -> ESCCommandArgumentDescriptor:
return ESCCommandArgumentDescriptor.new(
2,
[TYPE_STRING, TYPE_STRING, TYPE_STRING, TYPE_BOOL],
[null, null, "_front", false]
)


# Validate wether the given arguments match the command descriptor
func validate(arguments: Array):
if not escoria.object_manager.objects.has(arguments[0]):
escoria.logger.report_errors(
"queue_event.gd:validate",
[
"Object with global id %s not found" % arguments[0]
]
)
return false
var node = escoria.object_manager.objects.get(
arguments[0]
).node
if not "esc_script" in node or node.esc_script == "":
escoria.logger.report_errors(
"queue_event.gd:validate",
[
"Object with global id %s has no ESC script" % arguments[0]
]
)
return false
var esc_script = escoria.esc_compiler.load_esc_file(node.esc_script)
if not arguments[1] in esc_script.events:
escoria.logger.report_errors(
"queue_event.gd:validate",
[
"Event with name %s not found" % arguments[1]
]
)
return false
if arguments[3] and not escoria.event_manager.is_channel_free(arguments[2]):
escoria.logger.report_errors(
"queue_event.gd:validate",
[
"The queue %s doesn't accept a new event." % arguments[2]
]
)
return false
return .validate(arguments)


# Run the command
func run(arguments: Array) -> int:
var node = escoria.object_manager.objects.get(
arguments[0]
).node
var esc_script = escoria.esc_compiler.load_esc_file(node.esc_script)
if arguments[2] == "_front":
escoria.event_manager.queue_event(esc_script.events[arguments[1]])
else:
escoria.event_manager.queue_background_event(
arguments[2],
esc_script.events[arguments[1]]
)
if arguments[3]:
if arguments[2] == "_front":
var rc = yield(escoria.event_manager, "event_finished")
while rc[1] != arguments[1]:
rc = yield(escoria.event_manager, "event_finished")
return rc
else:
var rc = yield(
escoria.event_manager,
"background_event_finished"
)
while rc[1] != arguments[1] and rc[2] != arguments[2]:
rc = yield(
escoria.event_manager,
"background_event_finished"
)
return rc
return ESCExecution.RC_OK
210 changes: 149 additions & 61 deletions addons/escoria-core/game/core-scripts/esc/esc_event_manager.gd
Original file line number Diff line number Diff line change
@@ -1,106 +1,194 @@
# A manager for running events
# There are different "channels" an event can run on.
# The usual events happen in the foreground channel _front, but
# additional event queues can be added as required.
# Additionally, events can be scheduled to be queued in the future
extends Node
class_name ESCEventManager


# Emitted when the event started execution
signal event_started(event_name)

# Emitted when the event did finish running
signal event_finished(event_name, return_code)
# Emitted when an event is started in a channel of the background queue
signal background_event_started(channel_name, event_name)

# Emitted when the event was interrupted
signal event_interrupted(event_name, return_code)
# Emitted when the event did finish running
signal event_finished(return_code, event_name)

# Emitted when a background event was finished
signal background_event_finished(return_code, event_name, channel_name)

# A queue of events to run
var events_queue: Array = []

# A list of currently scheduled events
var scheduled_events: Array = []

# Currently running event
var _running_event: ESCEvent
# A list of constantly running events in multiple background channels
var events_queue: Dictionary = {
"_front": []
}

# Currently running event in background channels
var _running_events: Dictionary = {}

# Whether the event manager is allowed to proceed with next event.
var can_process_next_event = true
# Wether an event can be played on a specific channel
var _channels_state: Dictionary = {}


# Make sure to stop when pausing the game
func _ready():
self.pause_mode = Node.PAUSE_MODE_STOP


# Handle the events queue and scheduled events
#
# #### Parameters
# - delta: Time passed since the last process call
func _process(delta: float) -> void:
if events_queue.size() > 0 and can_process_next_event:
can_process_next_event = false
_running_event = events_queue.pop_front()
escoria.logger.debug(
"esc_event_manager",
[
"Popping event %s from event_queue" \
% _running_event.name if _running_event.get("name") != null \
else str(_running_event)
]
)
if not _running_event.is_connected(
"finished", self, "_on_event_finished"
):
_running_event.connect(
"finished",
self,
"_on_event_finished",
[_running_event]
)
if not _running_event.is_connected(
"interrupted", self, "_on_event_finished"
):
_running_event.connect(
"interrupted",
self,
"_on_event_finished",
[_running_event]
for channel_name in events_queue.keys():
if events_queue[channel_name].size() == 0:
continue
if is_channel_free(channel_name):
_channels_state[channel_name] = false
_running_events[channel_name] = \
events_queue[channel_name].pop_front()
escoria.logger.debug(
"esc_event_manager",
[
"Popping event %s from background queue %s" % [
_running_events[channel_name].name,
channel_name
]
]
)

emit_signal("event_started", _running_event.name)
_running_event.run()
if not _running_events[channel_name].is_connected(
"finished", self, "_on_event_finished"
):
_running_events[channel_name].connect(
"finished",
self,
"_on_event_finished",
[channel_name]
)
if not _running_events[channel_name].is_connected(
"interrupted", self, "_on_event_finished"
):
_running_events[channel_name].connect(
"interrupted",
self,
"_on_event_finished",
[channel_name]
)

if channel_name == "_front":
emit_signal(
"event_started",
_running_events[channel_name].name
)
else:
emit_signal(
"background_event_started",
channel_name,
_running_events[channel_name].name
)
_running_events[channel_name].run()
for event in self.scheduled_events:
(event as ESCScheduledEvent).timeout -= delta
if (event as ESCScheduledEvent).timeout <= 0:
self.scheduled_events.erase(event)
self.events_queue.append(event.event)
self.events_queue['_front'].append(event.event)


# Queue a new event to run
# Queue a new event to run in the foreground
#
# #### Parameters
# - event: Event to run
func queue_event(event: ESCEvent) -> void:
events_queue.append(event)
self.events_queue['_front'].append(event)


# Schedule an event to run after a timeout
#
# #### Parameters
# - event: Event to run
# - timeout: Number of seconds to wait before adding the event to the
# front queue
func schedule_event(event: ESCEvent, timeout: float) -> void:
scheduled_events.append(ESCScheduledEvent.new(event, timeout))


# Queue the run of an event in a background channel
#
# #### Parameters
# - channel_name: Name of the channel to use
# - event: Event to run
func queue_background_event(channel_name: String, event: ESCEvent) -> void:
if not channel_name in events_queue:
events_queue[channel_name] = []

events_queue[channel_name].append(event)


# Interrupt the events currently running.
func interrupt_running_event():
for channel_name in _running_events.keys():
if _running_events[channel_name] != null:
_running_events[channel_name].interrupt()


# Clears the event queues.
func clear_event_queue():
for channel_name in events_queue.keys():
events_queue[channel_name].clear()


# Check wether a channel is free to run more events
#
# #### Parameters
# - name: Name of the channel to test
# **Returns** Wether the channel can currently accept a new event
func is_channel_free(name: String) -> bool:
return _channels_state[name] if name in _channels_state else true


# Get the currently running event in a channel
#
# #### Parameters
# - name: Name of the channel
# **Returns** The currently running event or null
func get_running_event(name: String) -> ESCEvent:
return _running_events[name] if name in _running_events else null


# The event finished running
func _on_event_finished(return_code: int, event: ESCEvent) -> void:
#
# #### Parameters
# - return_code: Return code of the finished event
# - channel_name: Name of the channel that the event came from
func _on_event_finished(return_code: int, channel_name: String) -> void:
var event = _running_events[channel_name]
escoria.logger.debug(
"Event %s ended with return code %d" % [event.name, return_code]
)
event.disconnect("finished", self, "_on_event_finished")
event.disconnect("interrupted", self, "_on_event_finished")
_running_event = null
can_process_next_event = true
match(return_code):
ESCExecution.RC_CANCEL:
return_code = ESCExecution.RC_OK
emit_signal("event_finished", return_code, event.name)


# Interrupt the event currently running.
func interrupt_running_event():
if _running_event == null:
return
_running_event.interrupt()

if return_code == ESCExecution.RC_CANCEL:
return_code = ESCExecution.RC_OK

# Clears the event queue.
func clear_event_queue():
events_queue.clear()
_running_events[channel_name] = null
_channels_state[channel_name] = true

if channel_name == "_front":
emit_signal(
"event_finished",
return_code,
event.name
)
else:
emit_signal(
"background_event_finished",
return_code,
event.name,
channel_name
)
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ func prepare_arguments(arguments: Array) -> Array:

for index in range(arguments.size()):
complete_arguments[index] = escoria.utils.get_typed_value(
arguments[index]
arguments[index],
types[index]
)
var strip = strip_quotes[0]
if strip_quotes.size() == complete_arguments.size():
Expand Down
Loading

0 comments on commit 9adc7bb

Please sign in to comment.