// Copyright 2025 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
///|
pub type SystemFn[W] = (W) -> Unit
///|
pub type Plugin[W] = (App[W]) -> App[W]
///|
pub type RunConditionFn[W] = (W) -> Bool
///|
pub type SystemSet = String
///|
pub fn system_set(name : String) -> SystemSet {
name
}
///|
pub(all) enum ScheduleStage {
Startup
PreUpdate
FixedUpdate
Update
PostUpdate
} derive(Eq, Show)
///|
pub(open) trait SystemParam {
get() -> Self
}
///|
pub fn[W, P : SystemParam] system_param(system : (P) -> Unit) -> SystemFn[W] {
fn(_world : W) {
let param : P = SystemParam::get()
system(param)
}
}
///|
pub struct SystemConfig[W] {
system : SystemFn[W]
run_if : RunConditionFn[W]?
set : SystemSet?
before : Array[SystemSet]
after : Array[SystemSet]
mut last_run_tick : @core.Tick
}
///|
pub fn[W] system(system : SystemFn[W]) -> SystemConfig[W] {
SystemConfig::{
system,
run_if: None,
set: None,
before: [],
after: [],
last_run_tick: 0,
}
}
///|
pub fn[W] SystemConfig::run_if(
self : SystemConfig[W],
cond : RunConditionFn[W],
) -> SystemConfig[W] {
SystemConfig::{ ..self, run_if: Some(cond) }
}
///|
pub fn[W] SystemConfig::in_set(
self : SystemConfig[W],
set : SystemSet,
) -> SystemConfig[W] {
SystemConfig::{ ..self, set: Some(set) }
}
///|
pub fn[W] SystemConfig::before(
self : SystemConfig[W],
set : SystemSet,
) -> SystemConfig[W] {
self.before.push(set)
self
}
///|
pub fn[W] SystemConfig::after(
self : SystemConfig[W],
set : SystemSet,
) -> SystemConfig[W] {
self.after.push(set)
self
}
///|
pub type DeltaSecondsFn = () -> Float
///|
pub type RunnerFn = () -> Unit
///|
pub type RunnerBuilder[W] = (App[W]) -> RunnerFn
///|
pub struct Schedule[W] {
startup : Array[SystemConfig[W]]
pre_update : Array[SystemConfig[W]]
fixed_update : Array[SystemConfig[W]]
update : Array[SystemConfig[W]]
post_update : Array[SystemConfig[W]]
startup_set_edges : Array[(SystemSet, SystemSet)]
pre_update_set_edges : Array[(SystemSet, SystemSet)]
fixed_update_set_edges : Array[(SystemSet, SystemSet)]
update_set_edges : Array[(SystemSet, SystemSet)]
post_update_set_edges : Array[(SystemSet, SystemSet)]
}
///|
pub struct App[W] {
world : W
schedule : Schedule[W]
runner : RunnerFn?
}
///|
pub fn[W] App::new(world : W) -> App[W] {
App::{
world,
schedule: Schedule::{
startup: [],
pre_update: [],
fixed_update: [],
update: [],
post_update: [],
startup_set_edges: [],
pre_update_set_edges: [],
fixed_update_set_edges: [],
update_set_edges: [],
post_update_set_edges: [],
},
runner: None,
}
}
///|
pub fn[W] App::add_plugins(self : App[W], plugins : Array[Plugin[W]]) -> App[W] {
let mut app = self
for plugin in plugins {
app = plugin(app)
}
app
}
///|
pub fn[W] App::add_startup_system_config(
self : App[W],
config : SystemConfig[W],
) -> App[W] {
self.schedule.startup.push(config)
self
}
///|
pub fn[W] App::add_startup_system(
self : App[W],
system_fn : SystemFn[W],
) -> App[W] {
self.schedule.startup.push(system(system_fn))
self
}
///|
pub fn[W, P : SystemParam] App::add_startup_system_param(
self : App[W],
startup_system : (P) -> Unit,
) -> App[W] {
self.schedule.startup.push(system(system_param(startup_system)))
self
}
///|
pub fn[W] App::add_pre_update_system_config(
self : App[W],
config : SystemConfig[W],
) -> App[W] {
self.schedule.pre_update.push(config)
self
}
///|
pub fn[W] App::add_pre_update_system(
self : App[W],
system_fn : SystemFn[W],
) -> App[W] {
self.schedule.pre_update.push(system(system_fn))
self
}
///|
pub fn[W, P : SystemParam] App::add_pre_update_system_param(
self : App[W],
pre_update_system : (P) -> Unit,
) -> App[W] {
self.schedule.pre_update.push(system(system_param(pre_update_system)))
self
}
///|
pub fn[W] App::add_fixed_update_system_config(
self : App[W],
config : SystemConfig[W],
) -> App[W] {
self.schedule.fixed_update.push(config)
self
}
///|
pub fn[W] App::add_fixed_update_system(
self : App[W],
system_fn : SystemFn[W],
) -> App[W] {
self.schedule.fixed_update.push(system(system_fn))
self
}
///|
pub fn[W, P : SystemParam] App::add_fixed_update_system_param(
self : App[W],
fixed_update_system : (P) -> Unit,
) -> App[W] {
self.schedule.fixed_update.push(system(system_param(fixed_update_system)))
self
}
///|
pub fn[W] App::add_system_config(
self : App[W],
config : SystemConfig[W],
) -> App[W] {
self.schedule.update.push(config)
self
}
///|
pub fn[W] App::add_system(self : App[W], system_fn : SystemFn[W]) -> App[W] {
self.schedule.update.push(system(system_fn))
self
}
///|
pub fn[W, P : SystemParam] App::add_system_param(
self : App[W],
update_system : (P) -> Unit,
) -> App[W] {
self.schedule.update.push(system(system_param(update_system)))
self
}
///|
pub fn[W] App::add_post_update_system_config(
self : App[W],
config : SystemConfig[W],
) -> App[W] {
self.schedule.post_update.push(config)
self
}
///|
pub fn[W] App::add_post_update_system(
self : App[W],
system_fn : SystemFn[W],
) -> App[W] {
self.schedule.post_update.push(system(system_fn))
self
}
///|
pub fn[W, P : SystemParam] App::add_post_update_system_param(
self : App[W],
post_update_system : (P) -> Unit,
) -> App[W] {
self.schedule.post_update.push(system(system_param(post_update_system)))
self
}
///|
fn[W] schedule_systems(
schedule : Schedule[W],
stage : ScheduleStage,
) -> Array[SystemConfig[W]] {
match stage {
Startup => schedule.startup
PreUpdate => schedule.pre_update
FixedUpdate => schedule.fixed_update
Update => schedule.update
PostUpdate => schedule.post_update
}
}
///|
fn[W] schedule_set_edges(
schedule : Schedule[W],
stage : ScheduleStage,
) -> Array[(SystemSet, SystemSet)] {
match stage {
Startup => schedule.startup_set_edges
PreUpdate => schedule.pre_update_set_edges
FixedUpdate => schedule.fixed_update_set_edges
Update => schedule.update_set_edges
PostUpdate => schedule.post_update_set_edges
}
}
///|
pub fn[W] App::configure_set(
self : App[W],
stage : ScheduleStage,
set : SystemSet,
before? : Array[SystemSet] = [],
after? : Array[SystemSet] = [],
) -> App[W] {
let edges = schedule_set_edges(self.schedule, stage)
for other in before {
edges.push((set, other))
}
for other in after {
edges.push((other, set))
}
self
}
///|
priv struct FixedTimeState {
mut timestep_seconds : Float
mut max_delta_seconds : Float
mut overstep_seconds : Float
mut elapsed_seconds : Float
mut did_run_this_frame : Bool
mut steps_ran_this_frame : Int
}
///|
const DEFAULT_FIXED_TIMESTEP_SECONDS : Float = 0.015625F
///|
const DEFAULT_FIXED_MAX_DELTA_SECONDS : Float = 0.25F
///|
let fixed_time_state : Ref[FixedTimeState] = Ref::new(FixedTimeState::{
timestep_seconds: DEFAULT_FIXED_TIMESTEP_SECONDS,
max_delta_seconds: DEFAULT_FIXED_MAX_DELTA_SECONDS,
overstep_seconds: 0.0F,
elapsed_seconds: 0.0F,
did_run_this_frame: false,
steps_ran_this_frame: 0,
})
///|
let delta_seconds_provider : Ref[DeltaSecondsFn?] = Ref::new(None)
///|
pub fn set_delta_seconds_provider(provider : DeltaSecondsFn) -> Unit {
delta_seconds_provider.val = Some(provider)
}
///|
pub fn clear_delta_seconds_provider() -> Unit {
delta_seconds_provider.val = None
}
///|
pub fn fixed_time_reset() -> Unit {
fixed_time_state.val.overstep_seconds = 0.0F
fixed_time_state.val.elapsed_seconds = 0.0F
fixed_time_state.val.did_run_this_frame = false
fixed_time_state.val.steps_ran_this_frame = 0
}
///|
pub fn fixed_time_set_timestep_seconds(seconds : Float) -> Unit {
if seconds > 0.0F {
fixed_time_state.val.timestep_seconds = seconds
}
}
///|
pub fn fixed_time_set_max_delta_seconds(seconds : Float) -> Unit {
if seconds > 0.0F {
fixed_time_state.val.max_delta_seconds = seconds
}
}
///|
pub fn fixed_time_timestep_seconds() -> Float {
fixed_time_state.val.timestep_seconds
}
///|
pub fn fixed_time_overstep_seconds() -> Float {
fixed_time_state.val.overstep_seconds
}
///|
pub fn fixed_time_elapsed_seconds() -> Float {
fixed_time_state.val.elapsed_seconds
}
///|
pub fn fixed_time_overstep_fraction() -> Float {
let timestep = fixed_time_state.val.timestep_seconds
if timestep <= 0.0F {
0.0F
} else {
fixed_time_state.val.overstep_seconds / timestep
}
}
///|
pub fn fixed_time_did_run_this_frame() -> Bool {
fixed_time_state.val.did_run_this_frame
}
///|
pub fn fixed_time_steps_ran_this_frame() -> Int {
fixed_time_state.val.steps_ran_this_frame
}
///|
fn[W : @core.ChangeTickWorld + @core.SystemContextWorld] run_systems(
world : W,
systems : Array[SystemConfig[W]],
set_edges : Array[(SystemSet, SystemSet)],
) -> Unit {
let seen : @hashmap.HashMap[SystemSet, Bool] = @hashmap.HashMap::new()
let set_order : Array[SystemSet] = []
fn push_unique(
seen : @hashmap.HashMap[SystemSet, Bool],
set_order : Array[SystemSet],
set : SystemSet,
) -> Unit {
if !seen.contains(set) {
set_order.push(set)
seen.set(set, true)
}
}
let system_count = systems.length()
let groups : Array[SystemSet] = []
for i in 0.. Unit {
let from_i = set_index.at(from)
let to_i = set_index.at(to)
adj[from_i].push(to_i)
indeg[to_i] = indeg[to_i] + 1
}
for edge in set_edges {
add_edge(set_index, indeg, adj, edge.0, edge.1)
}
for i in 0.. Int {
let mut best_pos = 0
let mut best_value = ready[0]
let len = ready.length()
for i in 1.. ignore
best_value
}
let ready : Array[Int] = []
for i in 0.. 0 {
let next = pop_smallest(ready)
topo.push(next)
for to in adj[next] {
indeg[to] = indeg[to] - 1
if indeg[to] == 0 {
ready.push(to)
}
}
}
// Cycle fallback: preserve first-seen order.
if topo.length() != set_count {
topo.clear()
for i in 0.. App[W] {
run_systems(
self.world,
schedule_systems(self.schedule, Startup),
schedule_set_edges(self.schedule, Startup),
)
self
}
///|
pub fn[W : @core.ChangeTickWorld + @core.SystemContextWorld] App::run_once(
self : App[W],
) -> App[W] {
run_systems(
self.world,
schedule_systems(self.schedule, PreUpdate),
schedule_set_edges(self.schedule, PreUpdate),
)
fixed_time_state.val.did_run_this_frame = false
fixed_time_state.val.steps_ran_this_frame = 0
let delta_raw = match delta_seconds_provider.val {
Some(provider) => provider()
None => 0.0F
}
let delta_non_negative = if delta_raw < 0.0F { 0.0F } else { delta_raw }
let max_delta = fixed_time_state.val.max_delta_seconds
let delta = if delta_non_negative > max_delta {
max_delta
} else {
delta_non_negative
}
fixed_time_state.val.overstep_seconds = fixed_time_state.val.overstep_seconds +
delta
let timestep = fixed_time_state.val.timestep_seconds
if timestep > 0.0F {
let fixed_systems = schedule_systems(self.schedule, FixedUpdate)
let fixed_edges = schedule_set_edges(self.schedule, FixedUpdate)
let eps = 0.0000001F
while fixed_time_state.val.overstep_seconds + eps >= timestep {
fixed_time_state.val.overstep_seconds = fixed_time_state.val.overstep_seconds -
timestep
fixed_time_state.val.elapsed_seconds = fixed_time_state.val.elapsed_seconds +
timestep
fixed_time_state.val.did_run_this_frame = true
fixed_time_state.val.steps_ran_this_frame = fixed_time_state.val.steps_ran_this_frame +
1
run_systems(self.world, fixed_systems, fixed_edges)
}
}
run_systems(
self.world,
schedule_systems(self.schedule, Update),
schedule_set_edges(self.schedule, Update),
)
run_systems(
self.world,
schedule_systems(self.schedule, PostUpdate),
schedule_set_edges(self.schedule, PostUpdate),
)
self
}
///|
pub fn[W : @core.ChangeTickWorld + @core.SystemContextWorld] App::run(
self : App[W],
) -> Unit {
if self.runner is Some(runner) {
runner()
} else {
self.run_startup().run_once() |> ignore
}
}
///|
pub fn[W] App::set_runner(self : App[W], builder : RunnerBuilder[W]) -> App[W] {
let runner = builder(self)
App::{ ..self, runner: Some(runner) }
}