-
Notifications
You must be signed in to change notification settings - Fork 119
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: This introduces background events queue in Escoria (#444)
Co-authored-by: Dennis Ploeger <[email protected]>
- Loading branch information
Showing
12 changed files
with
319 additions
and
112 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
99 changes: 99 additions & 0 deletions
99
addons/escoria-core/game/core-scripts/esc/commands/queue_event.gd
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
210
addons/escoria-core/game/core-scripts/esc/esc_event_manager.gd
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.