API
Starlight
— ModuleMain module for Starlight.jl - a greedy framework for greedy developers.
Reexports
- Base: ReentrantLock, lock, unlock
- FileIO: load
- Pkg.Artifacts
- LazyArtifacts
- DataStructures: Queue, enqueue!, dequeue!
- DataFrames
- Colors
- SimpleDirectMediaLayer
- SimpleDirectMediaLayer.LibSDL2
- Telescope
Core
App and Events
Starlight.awake!
— Methodawake!(a) = nothing
Arbitrary startup function.
By default does literally nothing, but methods can be added for any type.
The preferred way to call listenFor is from inside an awake! method.
Starlight.shutdown!
— Methodshutdown!(a) = nothing
Arbitrary shutdown function.
By default does literally nothing, but methods can be added for any type.
The preferred way to call unlistenFrom is from inside a shutdown! method.
Starlight.listeners
— Constantconst listeners = Dict{DataType, Set{Any}}()
Internal dictionary that pairs event types with their listeners.
Starlight.messages
— Constantconst messages = Channel(Inf)
Internal Channel used to hold messages.
Starlight.listener_lock
— Constantconst listener_lock = ReentrantLock()
Internal lock used to synchronize access to the listeners dictionary.
Starlight.handleMessage!
— MethodhandleMessage!(l, m) = nothing
Invoked on listener l when message m is received.
Currently there are three ways this function can be invoked for a given listener and message:
- automatically by the message dispatcher, when the listener has been registered with listenFor and the message is of the corresponding type
- automatically by the Physics subsystem on two colliding objects with message type TS_CollisionEvent
- manually by user code (discouraged)
By default does (literally) nothing, but methods can be added for any type combination.
Starlight.sendMessage
— Methodfunction sendMessage(m)
if haskey(listeners, typeof(m))
put!(messages, m)
end
end
Add a message to the message queue if the message's type has any listeners. Otherwise drop.
The preferred way to send events/messages in Starlight.
Starlight.listenFor
— Methodfunction listenFor(e::Any, d::DataType)
lock(listener_lock)
if !haskey(listeners, d) listeners[d] = Set{Any}() end
push!(listeners[d], e)
unlock(listener_lock)
end
Add a listener e for messages of type d. l's handleMessage! method will be invoked when messages of type d are received.
Starlight.unlistenFrom
— Methodfunction unlistenFrom(e::Any, d::DataType)
lock(listener_lock)
if haskey(listeners, d) delete!(listeners[d], e) end
unlock(listener_lock)
end
Stop listener e from receiving messages of type d.
Starlight.handleException
— FunctionhandleException() -> Nothing
Print a stacktrace and exit safely.
Starlight.dispatchMessage
— Functionfunction dispatchMessage(arg)
try
m = take!(messages) # NOTE messages are fully processed in the order they are received
d = typeof(m)
if haskey(listeners, d)
# Threads.@threads doesn't work on raw sets
# because it needs to use indexing to split
# up memory, i work around it this way
Threads.@threads for l in Vector([listeners[d]...])
handleMessage!(l, m)
end
end
catch
handleException()
end
end
Take a single message from the event queue and invoke handleMessage! for all listeners in parallel.
If there are no listeners for the message type (i.e. they were removed after the message was sent), drop.
Normally runs in an infinite loop in a background Task started by awake!(::App).
This function is called internally by Starlight and documented here for completeness. Do not invoke it yourself unless you know what you are doing.
Starlight.App
— TypeStruct for the "master App" singleton.
Constructor accepts keyword arguments for all fields except systems, and no positional arguments.
Fields
- systems::Dict{DataType, Any} Dictionary of subsystems by type
- running::Bool Whether awake! has been called
- wdth::Int Window width
- hght::Int Window height
- bgrd::Colorant Window default background color
Starlight.on
— Functionon(a::App) = a.running
Check whether awake! has been called.
Starlight.off
— Functionoff(a::App) = !a.running
Opposite of on.
Starlight.system!
— Functionsystem!(a::App, s) = a.systems[typeof(s)] = s
Add something to an App's systems dictionary.
Starlight.clk
— Functionclk() = App().systems[Clock]
Get the current Clock.
Starlight.ecs
— Functionecs() = App().systems[ECS]
Get the current Entity Component System.
Starlight.inp
— Functioninp() = App().systems[Input]
Get the current Input manager.
Starlight.ts
— Functionts() = App().systems[TS]
Get the current Telescope backend subsystem.
Starlight.phys
— Functionphys() = App().systems[Physics]
Get the current Physics manager.
Starlight.scn
— Functionscn() = App().systems[Scene]
Get the current Scene graph.
Starlight.systemAwakeOrder
— FunctionsystemAwakeOrder = () -> [clk(), ts(), inp(), phys(), ecs(), scn()]
The order in which App subsystems should be awoken in order to not produce bugs.
Starlight.systemShutdownOrder
— FunctionsystemShutdownOrder = () -> [clk(), inp(), phys(), scn(), ecs(), ts()]
The order in which App subsystems should be shut down in order to not produce bugs.
Starlight.awake!
— Methodfunction awake!(a::App)
if !on(a)
job!(clk(), dispatchMessage)
map(awake!, systemAwakeOrder())
a.running = true
end
end
If not on, start the message dispatcher call awake! on all subsystems.
Note that if running from a script the app will still exit when Julia exits, it will never block. Figuring out whether/how to keep it alive is on the user. One method is to use run!, see below.
Starlight.shutdown!
— Methodfunction shutdown!(a::App)
if !off(a)
map(shutdown!, systemShutdownOrder())
a.running = false
end
end
If not off, call shutdown! on all subsystems.
Starlight.run!
— Functionfunction run!(a::App)
awake!(a)
if !isinteractive()
while on(a)
yield()
end
end
end
Call awake! and keep alive until switched off.
Clock
Starlight.Clock
— Typemutable struct Clock
start_event::Base.Event
is_stopped::Bool
frequency::AbstractFloat
Clock() = new(Base.Event(), true, 0.01667)
end
Fields
start_event
::Base.Event Whether awake! has been calledis_stopped
::Bool Whether shutdown! has been calledfrequency
::AbstractFloat Tick frequency
Starlight.TICK
— Typestruct TICK
Δ::AbstractFloat # seconds
end
Starlight.SLEEP_SEC
— Typestruct SLEEP_SEC
Δ::AbstractFloat
end
Starlight.SLEEP_NSEC
— Typestruct SLEEP_NSEC
Δ::UInt
end
Base.sleep
— Methodfunction Base.sleep(s::SLEEP_SEC) ::Float64
t1 = time()
while true
if time() - t1 >= s.Δ break end
yield()
end
return time() - t1
end
sleep(::SLEEP_SEC) -> Float64
Sleep the specified number of seconds.
Base.sleep
— Methodfunction Base.sleep(s::SLEEP_NSEC)
t1 = time_ns()
while true
if time_ns() - t1 >= s.Δ break end
yield()
end
return time_ns() - t1
end
sleep(::SLEEP_NSEC) -> UInt64
Sleep the specified number of nanoseconds
Starlight.tick
— Functionfunction tick(Δ)
δ = sleep(SLEEP_NSEC(Δ * 1e9))
sendMessage(TICK(δ / 1e9))
end
tick(::Any) -> Nothing
Sleep Δ seconds, the raise a TICK event with the actual amount of time slept. Called in a background task in an infinite loop. Primary purpose is to trigger subsystem TICK handlers.
Starlight.job!
— Functionfunction job!(c::Clock, f, arg=1)
function job()
Base.wait(c.start_event)
while !c.is_stopped
f(arg)
end
end
schedule(Task(job))
end
job!(::Clock, f::Any, arg::Any) -> Task
Schedule a background task to be run and synchronized with clock state. Used internally to run dispatchMessage and tick.
Starlight.oneshot!
— Functionfunction oneshot!(c::Clock, f, arg=1)
function oneshot()
Base.wait(c.start_event)
f(arg)
end
schedule(Task(oneshot))
end
oneshot!(::Clock, f::Any, arg::Any) -> Task
Schedule a background task to be run once, synchronized with Clock state.
Starlight.awake!
— Methodfunction awake!(c::Clock)
job!(c, tick, c.frequency)
c.is_stopped = false
Base.notify(c.start_event)
end
awake!(::Clock) -> Nothing
Starts the tick job and signals all waiting Tasks that the clock has started.
Starlight.shutdown!
— Methodfunction shutdown!(c::Clock)
c.is_stopped = true
c.start_event = Base.Event()
end
shutdown!(::Clock) -> Nothing
Signal Tasks that the Clock has stopped.