Register, update and discard events in a timeline.

A queue of event is used to register objects associated with a start time and a duration and to return them when they are active.

The main class is EventQueue. It controls a timeline and register events. Event can be any kind of object, usually specific time-related domain objects should be used.

The auxiliary class EventInfo registers and control information about each event. It can also be used for fluent programming and create new events relatively to existing ones.

Basic usage

The event queue is created empty, and can handle any kind of data.

var eq = new EventQueue[String]

To register an event, add it with a start time and a duration. Note that the time is relative to the current time frame in the queue.

eq.add("1..2", 1.0, 1.0)
eq.add("2..10", 2.0, 8.0)

To register instantaneous event, use a 0.0 duration (or no duration)

eq.add("1.5", 1.5)

To get active events, use update with a time difference. This returns a sequence of currently active events.

var es = eq.update(1.0) # What is active after 1 unit of time?
assert es.length == 1

Each update increments the time frame of the queue and returns an updated information about active events.

es = eq.update(1.0) # What is active after an other unit of time?
assert eq.time == 2.0

assert es.length == 3

Results of the update method contains a lot of information.

Obviously the original events:

assert es[0].event == "1..2"
assert es[1].event == "1.5"
assert es[2].event == "2..10"

The time since the begin of the event:

assert es[0].time == 1.0 # Started 1 unit of time ago
assert es[1].time == 0.5
assert es[2].time == 0.0

The completion ratio of the event until its termination:

assert es[0].completion == 1.0 # has just finished
assert es[1].completion == inf # completion ratio does not make sense for instant events
assert es[2].completion == 0.0

And a lot of other things, just look at EventInfo.

Expiration and control

When update returns events, occurrences count the number of time an event is returned by update. It can be used for instance to see if it is the first time that a specific event is active.

assert es[0].occurrences == 2 # it is the second time we see it
assert es[1].occurrences == 1 # instant one
assert es[2].occurrences == 1

The duration is used to automatically manage the expiration of the event.

Note that if a event expires during an update, then it is still returned by the function. This is the way to handle instant events and newly expired events.

To distinguish them has_expired can be used.

assert es[0].has_expired == true  # just finished
assert es[1].has_expired == true  # instant one
assert es[2].has_expired == false

On the next update, the already expired events are not returned again.

es = eq.update(1.0) # What is active after an other unit of time?
assert eq.time == 3.0
assert es.length == 1
assert es.first.event == "2..10"

The expire method can force the expiration of an event.

es.first.expire
es = eq.update(1.0)
assert es.is_empty

Fluent programming

The add method (and its derivates) returns a EventInfo object that can be used to have information about the newly registered event but also to chain the creation of time-related events.

For instance, the following example registers 4 events, each one relative to the previous one:

eq = new EventQueue[String]
eq.add("e1", 10.0, 5.0).
   add_after("e2", 2.0, 5.0).
   add_sync("e3", 2.0, 5.0).
   add_before("e4", 2.0, 5.0)

This can be decomposed as:

eq = new EventQueue[String]
var e1 = eq.add("e1", 10.0, 5.0)
assert e1.start == 10.0 # First event starts at 10

var e2 = e1.add_after("e2", 2.0, 5.0) # starts 2 units of time after the end of e1
assert e2.start == 17.0 # So starts at 10 + 5 + 2

var e3 = e2.add_sync("e3", 2.0, 5.0) # starts 2 units of time after the begin of e2
assert e3.start == 19.0 # So starts at 17 + 2

var e4 = e3.add_before("e4", 2.0, 5.0) # ends 2 units of time before the start of e3
assert e4.start == 12.0

Introduced classes

class EventInfo[E: nullable Object]

event_queue :: EventInfo

Information and management about a registered event
class EventQueue[E: nullable Object]

event_queue :: EventQueue

Queuing and management of arbitrary events in a timeline

All class definitions

class EventInfo[E: nullable Object]

event_queue $ EventInfo

Information and management about a registered event
class EventQueue[E: nullable Object]

event_queue $ EventQueue

Queuing and management of arbitrary events in a timeline
package_diagram event_queue::event_queue event_queue core core event_queue::event_queue->core a_star-m a_star-m a_star-m->event_queue::event_queue

Ancestors

module abstract_collection

core :: abstract_collection

Abstract collection classes and services.
module abstract_text

core :: abstract_text

Abstract class for manipulation of sequences of characters
module array

core :: array

This module introduces the standard array structure.
module bitset

core :: bitset

Services to handle BitSet
module bytes

core :: bytes

Services for byte streams and arrays
module circular_array

core :: circular_array

Efficient data structure to access both end of the sequence.
module codec_base

core :: codec_base

Base for codecs to use with streams
module codecs

core :: codecs

Group module for all codec-related manipulations
module collection

core :: collection

This module define several collection classes.
module environ

core :: environ

Access to the environment variables of the process
module error

core :: error

Standard error-management infrastructure.
module exec

core :: exec

Invocation and management of operating system sub-processes.
module file

core :: file

File manipulations (create, read, write, etc.)
module fixed_ints

core :: fixed_ints

Basic integers of fixed-precision
module fixed_ints_text

core :: fixed_ints_text

Text services to complement fixed_ints
module flat

core :: flat

All the array-based text representations
module gc

core :: gc

Access to the Nit internal garbage collection mechanism
module hash_collection

core :: hash_collection

Introduce HashMap and HashSet.
module iso8859_1

core :: iso8859_1

Codec for ISO8859-1 I/O
module kernel

core :: kernel

Most basic classes and methods.
module list

core :: list

This module handle double linked lists
module math

core :: math

Mathematical operations
module native

core :: native

Native structures for text and bytes
module numeric

core :: numeric

Advanced services for Numeric types
module protocol

core :: protocol

module queue

core :: queue

Queuing data structures and wrappers
module range

core :: range

Module for range of discrete objects.
module re

core :: re

Regular expression support for all services based on Pattern
module ropes

core :: ropes

Tree-based representation of a String.
module sorter

core :: sorter

This module contains classes used to compare things and sorts arrays.
module stream

core :: stream

Input and output streams of characters
module text

core :: text

All the classes and methods related to the manipulation of text entities
module time

core :: time

Management of time and dates
module union_find

core :: union_find

union–find algorithm using an efficient disjoint-set data structure
module utf8

core :: utf8

Codec for UTF-8 I/O

Parents

module core

core :: core

Standard classes and methods used by default by Nit programs and libraries.

Children

module a_star-m

a_star-m

# Register, update and discard events in a timeline.
#
# A queue of event is used to register objects associated with a start time and
# a duration and to return them when they are active.
#
# The main class is `EventQueue`. It controls a timeline and register events.
# Event can be any kind of object, usually specific time-related domain objects should be used.
#
# The auxiliary class `EventInfo` registers and control information about each event.
# It can also be used for fluent programming and create new events relatively to existing ones.
#
# ## Basic usage
#
# The event queue is created empty, and can handle any kind of data.
#
# ~~~
# var eq = new EventQueue[String]
# ~~~
#
# To register an event, add it with a start time and a duration.
# Note that the time is relative to the current time frame in the queue.
#
# ~~~
# eq.add("1..2", 1.0, 1.0)
# eq.add("2..10", 2.0, 8.0)
# ~~~
#
# To register instantaneous event, use a 0.0 duration (or no duration)
#
# ~~~
# eq.add("1.5", 1.5)
# ~~~
#
# To get active events, use `update` with a time difference.
# This returns a sequence of currently active events.
#
# ~~~
# var es = eq.update(1.0) # What is active after 1 unit of time?
# assert es.length == 1 # only "1..2" is active
# ~~~
#
# Each `update` increments the time frame of the queue and returns an updated information about active events.
#
# ~~~
# es = eq.update(1.0) # What is active after an other unit of time?
# assert eq.time == 2.0
#
# assert es.length == 3 # there is now 3 active events
# ~~~
#
# Results of the `update` method contains a lot of information.
#
# Obviously the original events:
#
# ~~~
# assert es[0].event == "1..2"
# assert es[1].event == "1.5"
# assert es[2].event == "2..10"
# ~~~
#
# The time since the begin of the event:
#
# ~~~
# assert es[0].time == 1.0 # Started 1 unit of time ago
# assert es[1].time == 0.5
# assert es[2].time == 0.0 # Started just now
# ~~~
#
# The completion ratio of the event until its termination:
#
# ~~~
# assert es[0].completion == 1.0 # has just finished
# assert es[1].completion == inf # completion ratio does not make sense for instant events
# assert es[2].completion == 0.0 # has just started
# ~~~
#
# And a lot of other things, just look at `EventInfo`.
#
# ## Expiration and control
#
# When `update` returns events, `occurrences` count the number of time an
# event is returned by update. It can be used for instance to see if it
# is the first time that a specific event is active.
#
# ~~~
# assert es[0].occurrences == 2 # it is the second time we see it
# assert es[1].occurrences == 1 # instant one
# assert es[2].occurrences == 1 # first time we see it
# ~~~
#
# The duration is used to automatically manage the expiration of the event.
#
# Note that if a event expires during an `update`, then it is still returned by the function.
# This is the way to handle instant events and newly expired events.
#
# To distinguish them `has_expired` can be used.
#
# ~~~
# assert es[0].has_expired == true  # just finished
# assert es[1].has_expired == true  # instant one
# assert es[2].has_expired == false # sill active
# ~~~
#
# On the next `update`, the already expired events are not returned again.
#
# ~~~
# es = eq.update(1.0) # What is active after an other unit of time?
# assert eq.time == 3.0
# assert es.length == 1
# assert es.first.event == "2..10" # No more "1..2" or "1.5"
# ~~~
#
# The `expire` method can force the expiration of an event.
#
# ~~~
# es.first.expire
# es = eq.update(1.0)
# assert es.is_empty # Sorry, "2..10" was expired
# ~~~
#
# ## Fluent programming
#
# The `add` method (and its derivates) returns a `EventInfo` object that can be used to have
# information about the newly registered event but also to chain the creation of time-related events.
#
# For instance, the following example registers 4 events, each one relative to the previous one:
#
# ~~~
# eq = new EventQueue[String]
# eq.add("e1", 10.0, 5.0).
#    add_after("e2", 2.0, 5.0).
#    add_sync("e3", 2.0, 5.0).
#    add_before("e4", 2.0, 5.0)
# ~~~
#
# This can be decomposed as:
#
# ~~~
# eq = new EventQueue[String]
# var e1 = eq.add("e1", 10.0, 5.0)
# assert e1.start == 10.0 # First event starts at 10
#
# var e2 = e1.add_after("e2", 2.0, 5.0) # starts 2 units of time after the end of e1
# assert e2.start == 17.0 # So starts at 10 + 5 + 2
#
# var e3 = e2.add_sync("e3", 2.0, 5.0) # starts 2 units of time after the begin of e2
# assert e3.start == 19.0 # So starts at 17 + 2
#
# var e4 = e3.add_before("e4", 2.0, 5.0) # ends 2 units of time before the start of e3
# assert e4.start == 12.0 # So starts at 19 - 2 - 5
# ~~~
module event_queue

# Queuing and management of arbitrary events in a timeline
class EventQueue[E]
	# Comparator used by the queue
	private var event_comparator = new EventComparator

	# Efficient queue
	private var queue = new MinHeap[EventInfo[E]](event_comparator)

	# List of current active event
	private var actives = new Array[EventInfo[E]]

	# List of previously active event
	private var old_actives = new Array[EventInfo[E]]

	# Global time since the creation of the queue
	var time = 0.0

	# Nearest deadline, used to optimise queue access
	#
	# `inf` if no deadline is set
	private var next: Float = inf

	# Register an `event` in the queue to be active in `delay` units of time.
	#
	# If `duration` is given, this is duration after what the event is automatically discarded.
	# If `null`, 0.0 or not given, the event will be executed only once
	# Use `inf` for an event with an infinite duration.
	fun add(event: E, delay: Float, duration: nullable Float): EventInfo[E]
	do
		return add_at(event, time + delay, duration)
	end

	# Register an `event` in the queue executed at a specific time.
	fun add_at(event: E, start: Float, duration: nullable Float): EventInfo[E]
	do
		if start < next then next = start
		var ei = new EventInfo[E](event, self, start, duration or else 0.0)
		queue.add ei

		return ei
	end

	# Add a given amount of time and return all the events that were active during the delay.
	#
	# Note that events that expired during the delay are marked `has_expired` and are still returned.
	fun update(dt: Float): SequenceRead[EventInfo[E]]
	do
		var time = self.time
		time += dt
		self.time = time

		# Switch things
		var tmp = old_actives
		old_actives = actives
		actives = tmp

		# Discard dead events
		actives.clear
		for ei in old_actives do
			if not ei.has_expired then actives.add ei
		end

		# Start new events
		if time >= next then loop
			if queue.is_empty then
				next = inf
				break
			end
			var ei = queue.peek
			if ei.start > time then
				next = ei.start
				break
			end
			ei = queue.take
			if not ei.has_expired then
				actives.add ei
				ei.occurrences = 0
			end
		end

		if actives.is_empty then return actives

		# Update event information
		for ei in actives do ei.update(time, dt)

		return actives
	end
end

# Information and management about a registered event
#
# It allows to retrieve static and dynamic information about an event.
# It also allows to register other time-related events with a fluent programming way.
class EventInfo[E]
	# The registered event.
	var event: E

	# The associated event queue.
	#
	# It is used internally for fluent programming.
	var queue: EventQueue[E]

	# Absolute start time for the registered event in the event queue time frame.
	var start: Float

	# Registered duration.
	#
	# Events with a 0.0 duration are called *instant events*.
	var duration: Float

	# Time since the begin of the event, in units of time.
	var time: Float = nan

	# Time since the last update (or the begin of the event if smaller)
	#
	# Note that if the event has expired during the last time lapse,
	# then `dt` also counts the *expired* time between the expiration time
	# and the update time.
	var dt: Float = nan

	# Ratio of completion
	#
	# Usually between 0.0 and 1.
	# It might be > 1.0 if the event has expired during the last time lapse.
	var completion = 0.0

	# Number of times that an event was returned by `update`.
	#
	# If the event just started during the last update, 1 is returned.
	#
	# If the event was not returned by `update` yet, 0 is returned.
	var occurrences = 0

	# Has the event expired?
	#
	# Such an event will be not present in the next `update`.
	#
	# Note that an event can be `has_expired` and have `occurrences == 0` if it
	# is entirely included in the last time lapse.
	# It is especially the case for instant events.
	var has_expired = false

	## Fluent methods

	# Register an event that starts after the end of the current one.
	#
	# `delay` indicates the time between the end of the current event and the begin of the new one.
	# Use 0.0 if both events should be contiguous and not overlap.
	#
	# Returns the new event information that can be used in fluent programming.
	fun add_after(event: E, delay: Float, duration: nullable Float): EventInfo[E]
	do
		return queue.add_at(event, start + self.duration + delay, duration)
	end

	# Register an event that starts with the current one.
	#
	# `delay` indicates the time between the begin of the current event and the begin of the new one.
	# Use 0.0 if both events should start at the same time and overlaps.
	#
	# Returns the new event information that can be used in fluent programming.
	fun add_sync(event: E, delay: Float, duration: nullable Float): EventInfo[E]
	do
		return queue.add_at(event, start + delay, duration)
	end

	# Register an event that finishes before the begin of current one.
	#
	# `delay` indicates the time between the end of the new event and the begin of the current one.
	# Use 0.0 if both event should be contiguous and not overlap.
	#
	# Returns the new event information that can be used in fluent programming.
	fun add_before(event: E, delay: Float, duration: nullable Float): EventInfo[E]
	do
		duration = duration or else 0.0
		return queue.add_at(event, start - delay - duration, duration)
	end

	# Update attributes. Is called by `EventQueue::update`
	private fun update(queue_time, queue_dt: Float)
	do
		time = queue_time - start
		if time >= duration then expire
		dt = queue_dt.min(time)
		completion = time / duration
		occurrences += 1
	end

	# Force the event to expire.
	#
	# The event can be active of not.
	#
	# ~~~
	# var eq = new EventQueue[String]
	# var e1 = eq.add("active", 0.0, 10.0)
	# var e2 = eq.add("not active", 2.0, 10.0)
	# var es = eq.update(1.0)
	# assert es == [e1]
	# e1.expire
	# e2.expire
	# es = eq.update(2.0)
	# assert es.is_empty
	# ~~~
	#
	# Note that when an event is forced to expire,
	# it will not appears in the next `update`.
	fun expire do has_expired = true

	redef fun to_s do return "{event or else "null"}@{start}+{duration}"
end

private class EventComparator
	super Comparator
	redef type COMPARED: EventInfo[nullable Object]
	redef fun compare(a, b) do return a.start <=> b.start
end
lib/event_queue/event_queue.nit:15,1--383,3