Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
A special event manager for keeping persistent game time.
# when loading a save, you'd use this to simulate time since last save
# EventManager.simulate_time_span(LAST_UNIX_TIMESTAMP, THIS_UNIX_TIMESTAMP)
# for example, this will simulate the last 30 seconds before the game opened
# EventManager.simulate_time_span(OS.get_unix_time() - 30, OS.get_unix_time())
# there is an optional third argument to that that specifies a minimum priority
# EventManager.simulate_time_span(LAST_UNIX_TIMESTAMP, THIS_UNIX_TIMESTAMP, Priority.UNSKIPPABLE)
# .. will only run unskippable events
# if the third argument is not specified, both skippable and unskippable events are run
# some examples of event types:
# add_event(
# PeriodicEvent.new(
# self,
# "periodic",
# Priority.UNSKIPPABLE,
# OS.get_unix_time(),
# time_in_seconds(0, 0, 5)
# )
# )
# .. runs every 5 seconds
# add_event(
# DelayedEvent.new(
# self,
# "delayed",
# Priority.UNSKIPPABLE,
# OS.get_unix_time(),
# time_in_seconds(0, 0, 10)
# )
# )
# .. runs 10 seconds after added, then removes itself
# add_event(
# DailyEvent.new(
# self,
# "midnight_utc",
# Priority.UNSKIPPABLE,
# time_in_seconds(4, 0, 0)
# )
# )
# .. runs every day at 4AM UTC
extends Node
enum Priority {
SKIPPABLE = 1,
UNSKIPPABLE
}
# contains TimedEvents we're monitoring
var events : Array = []
# uses a timer for updates once the simulation is caught up
var timer : Timer
var update_frequency : float = 1
# time the last update occurred
var last_time : int
func _ready () -> void:
# init the update timer
timer = Timer.new()
timer.wait_time = update_frequency
timer.one_shot = false
timer.autostart = true
timer.connect("timeout", self, "update_simulation")
add_child(timer)
last_time = OS.get_unix_time()
# called by timer
func update_simulation () -> void:
var current_time : int = OS.get_unix_time()
simulate_time_span(last_time, current_time)
last_time = current_time
# converts a length of time into seconds, convenience function
func time_in_seconds (hours : int, minutes : int, seconds : int) -> int:
return hours * 60 * 60 + minutes * 60 + seconds
# adds a TimedEvent to watch
func add_event (time_event : TimedEvent) -> void:
events.append(time_event)
# simulates a span of time
func simulate_time_span (start : int, end : int, minimum_priority : int = 1) -> void:
# delete those marked expired
for i in range(len(events) - 1, -1, -1):
if events[i].expired:
events.erase(events[i])
var due_events : Array = []
for event in events:
if event.priority >= minimum_priority:
event.populate(due_events, start, end)
due_events.sort_custom(self, "compare_events")
for event in due_events:
if event.timed_event.object.has_method(event.timed_event.method_name):
event.timed_event.object.call(event.timed_event.method_name)
else:
print("could not call method '%s' on %s, doesn't exist" % [event.timed_event.method_name, event.timed_event.object])
# a comparitor for sorting by time of event
# not always necessary, but handy for interdependent events
func compare_events (a : EventInstance, b : EventInstance) -> bool:
return a.time < b.time
# a single instance of a timed event
class EventInstance:
var timed_event : TimedEvent
var time : int
var priority : int
func _init(timed_event : TimedEvent, time : int):
self.timed_event = timed_event
self.time = time
# timed event base class
class TimedEvent:
var object : Object
var method_name : String
# if expired is true, event will be deleted during next simulation
var expired : bool = false
var priority : int = EventManager.Priority.SKIPPABLE
func _init (object : Object, method_name : String, priority : int):
self.object = object
self.method_name = method_name
self.priority = priority
# convenience function for checking if a timestamp falls within a timespan
func in_span (time : int, start : int, end : int) -> bool:
return time >= start and time < end
# populates an array of eventinstances for a span of time
# just a stub to be filled out by child classes
func populate (due_events : Array, span_start : int, span_end : int) -> void:
pass
# used for delayed events, e.g. 1 hour from now, and only once
class DelayedEvent extends TimedEvent:
var start_time : int
var delay_length : int
func _init(object : Object, method_name : String, priority : int, start_time : int, delay_length : int) . (object, method_name, priority):
self.start_time = start_time
self.delay_length = delay_length
func populate(due_events : Array, span_start : int, span_end : int) -> void:
if in_span(start_time + delay_length, span_start, span_end):
due_events.append(EventInstance.new(self, start_time + delay_length))
expired = true
# used for periodic events, e.g. every 15 minutes
class PeriodicEvent extends TimedEvent:
var start_time : int
var period_length : int
func _init(object : Object, method_name : String, priority : int, start_time : int, period_length : int) . (object, method_name, priority):
self.start_time = start_time
self.period_length = period_length
func populate (due_events : Array, span_start : int, span_end : int) -> void:
var remainder : int = start_time % period_length
var periods_in_span : int = (span_end - span_start) / period_length
if periods_in_span > 0:
var current_period : int = span_start / period_length
for i in range(0, periods_in_span):
due_events.append(EventInstance.new(self, (current_period + i) * period_length + remainder))
else:
var next_period : int = span_start / period_length
var next_time : int = next_period * period_length
next_time += remainder
if in_span(next_time, span_start, span_end):
due_events.append(EventInstance.new(self, next_time))
# used for daily events, e.g. every day at 5AM
class DailyEvent extends TimedEvent:
var time_of_day : int
func _init (object : Object, method_name : String, priority : int, time_of_day : int = 0) . (object, method_name, priority):
self.time_of_day = time_of_day
func populate (due_events : Array, span_start : int, span_end : int) -> void:
var span_of_day = EventManager.time_in_seconds(24, 0, 0)
var days_in_span = (span_end - span_start) / span_of_day
if days_in_span > 0:
var start_day = span_start / span_of_day
for day in range(start_day, start_day + days_in_span):
due_events.append(EventInstance.new(self, span_of_day * day + time_of_day))
else:
var next_period : int = span_start / span_of_day
var next_time : int = next_period * span_of_day + time_of_day
if in_span(next_time, span_start, span_end):
due_events.append(EventInstance.new(self, next_time))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment