///|
pub struct AppRuntime {
priv state : RuntimeState
priv program_runtime_snapshot_fn : () -> ProgramRuntimeSnapshot
priv destroy_program_runtime_fn : Ref[() -> Unit]
}
///|
fn program_message_drain_limit() -> Int {
1024
}
///|
fn[Msg] compact_pending_program_messages(
pending_messages : Array[Msg],
start : Int,
) -> Unit {
let remaining : Array[Msg] = []
for index in start.. Unit,
start_task : (@core.EffectDescriptor, ((Msg) -> Unit) -> (() -> Unit)?) -> Unit,
) -> Int {
let mut count = 0
effect.runtime_visit(
message => {
dispatch(message)
count = count + 1
},
run => {
run(dispatch)
count = count + 1
},
(_descriptor, run) => {
run(dispatch)
count = count + 1
},
(descriptor, start) => {
start_task(descriptor, start)
count = count + 1
},
)
count
}
///|
pub fn[Msg] AppRuntime::new_view(
root~ : @core.View[Msg],
size~ : @core.Size,
environment? : @core.Environment = @core.Environment::neutral(),
on_message? : ((Msg) -> Unit)? = None,
) -> AppRuntime {
{
state: RuntimeState::new_view(root~, size~, environment~, on_message~),
program_runtime_snapshot_fn: () => program_runtime_snapshot_empty(),
destroy_program_runtime_fn: Ref(() => ()),
}
}
///|
pub fn[Model, Msg] AppRuntime::new_program(
program~ : @core.Program[Model, Msg],
size~ : @core.Size,
environment? : @core.Environment = @core.Environment::neutral(),
) -> AppRuntime {
let (initial_model, initial_effect) = program.runtime_init()
let model = Ref(initial_model)
let program_runtime = ProgramRuntimeDiagnostics::new()
let pending_messages : Array[Msg] = []
let pending_index = Ref(0)
let dispatching = Ref(false)
let syncing_subscriptions = Ref(false)
let message_batch_depth = Ref(0)
let program_active = Ref(true)
let runtime_ref : Ref[AppRuntime?] = Ref(None)
let process_ref : Ref[(() -> Unit)?] = Ref(None)
let pending_count = () => pending_messages.length() - pending_index.val
let dispatch = message => {
if program_active.val {
pending_messages.push(message)
program_runtime.record_message_enqueued(pending_count())
if !syncing_subscriptions.val && message_batch_depth.val == 0 {
match process_ref.val {
Some(process) => process()
None => ()
}
}
} else {
program_runtime.record_ignored_program_dispatch()
}
}
let sync_subscriptions = () => {
syncing_subscriptions.val = true
program_runtime.sync_subscriptions(
program.runtime_subscriptions(model.val),
dispatch,
)
syncing_subscriptions.val = false
}
let sync_commands = () => {
match runtime_ref.val {
Some(runtime) =>
runtime.state.set_action_commands(
ActionCommandMap::new(
bindings=program
.runtime_commands(model.val)
.map(program_command => {
@core.CommandBinding::new(command=program_command.command(), handle=() => {
dispatch(program_command.message())
})
}),
),
)
None => ()
}
}
let begin_message_batch = () => {
message_batch_depth.val = message_batch_depth.val + 1
}
let end_message_batch = () => {
message_batch_depth.val = message_batch_depth.val - 1
if message_batch_depth.val == 0 && !syncing_subscriptions.val {
match process_ref.val {
Some(process) => process()
None => ()
}
}
}
let process_messages = () => {
if !dispatching.val && !syncing_subscriptions.val {
program_runtime.record_message_drain_started(pending_count())
dispatching.val = true
let mut processed_this_drain = 0
while pending_index.val < pending_messages.length() &&
processed_this_drain < program_message_drain_limit() {
let message = pending_messages[pending_index.val]
pending_index.val = pending_index.val + 1
processed_this_drain = processed_this_drain + 1
program_runtime.record_dispatch()
let (next_model, effect) = program.runtime_update(model.val, message)
program_runtime.record_update()
model.val = next_model
match runtime_ref.val {
Some(runtime) => runtime.state.request_root_rebuild()
None => ()
}
sync_commands()
let effect_summary = effect_plan_summary(effect)
let effect_count = run_effect(effect, dispatch, (descriptor, start) => {
program_runtime.start_effect_task(descriptor, start, dispatch)
})
program_runtime.record_effects(effect_summary, effect_count)
sync_subscriptions()
program_runtime.record_message_processed(pending_count())
}
if pending_index.val < pending_messages.length() {
compact_pending_program_messages(pending_messages, pending_index.val)
pending_index.val = 0
program_runtime.record_message_processed(pending_count())
} else {
pending_messages.clear()
pending_index.val = 0
program_runtime.record_message_queue_cleared()
}
dispatching.val = false
}
}
process_ref.val = Some(process_messages)
let initial_effect_summary = effect_plan_summary(initial_effect)
let initial_effect_count = run_effect(initial_effect, dispatch, (
descriptor,
start,
) => program_runtime.start_effect_task(descriptor, start, dispatch))
program_runtime.record_effects(initial_effect_summary, initial_effect_count)
sync_subscriptions()
match process_ref.val {
Some(process) => process()
None => ()
}
let runtime = AppRuntime::{
state: RuntimeState::new_view(
root=program.runtime_view(
model.val,
@core.ViewEnvironment::new(viewport_size=size, environment~),
),
size~,
environment~,
on_message=Some(dispatch),
begin_message_batch~,
end_message_batch~,
),
program_runtime_snapshot_fn: () => program_runtime.snapshot(),
destroy_program_runtime_fn: Ref(() => ()),
}
runtime_ref.val = Some(runtime)
runtime.state.set_root_builder(() => {
adapt_view_for_runtime(
program.runtime_view(
model.val,
@core.ViewEnvironment::new(
viewport_size=runtime.state.size,
environment=runtime.state.environment,
),
),
dispatch,
)
})
sync_commands()
runtime.destroy_program_runtime_fn.val = () => {
if program_active.val {
program_active.val = false
program_runtime.cancel_all_effect_tasks(EffectTaskRuntimeDestroyed)
program_runtime.cancel_all_subscriptions(SubscriptionRuntimeDestroyed)
}
}
runtime
}
///|
pub fn[Msg] new_view(
root~ : @core.View[Msg],
size~ : @core.Size,
environment? : @core.Environment = @core.Environment::neutral(),
on_message? : ((Msg) -> Unit)? = None,
) -> AppRuntime {
AppRuntime::new_view(root~, size~, environment~, on_message~)
}
///|
pub fn[Model, Msg] new_program(
program~ : @core.Program[Model, Msg],
size~ : @core.Size,
environment? : @core.Environment = @core.Environment::neutral(),
) -> AppRuntime {
AppRuntime::new_program(program~, size~, environment~)
}
///|
pub fn[Model, Msg] new_program_with_dimensions(
program~ : @core.Program[Model, Msg],
width~ : Double,
height~ : Double,
environment? : @core.Environment = @core.Environment::neutral(),
) -> AppRuntime {
AppRuntime::new_program(
program~,
size=@core.Size::new(width~, height~),
environment~,
)
}
///|
pub fn AppRuntime::environment(self : AppRuntime) -> @core.Environment {
self.state.environment()
}
///|
pub fn AppRuntime::text_system(self : AppRuntime) -> @core.TextSystem {
self.state.text_system()
}
///|
pub fn AppRuntime::set_text_system(
self : AppRuntime,
text_system : @core.TextSystem,
) -> Unit {
self.state.set_text_system(text_system)
}
///|
pub fn AppRuntime::register_font_data(
self : AppRuntime,
family : @core.FontFamily,
data : Bytes,
) -> Unit {
self.state.register_font_data(family, data)
}
///|
pub fn AppRuntime::set_environment(
self : AppRuntime,
environment : @core.Environment,
) -> Unit {
self.state.set_environment(environment)
}
///|
pub fn AppRuntime::advance_time(self : AppRuntime, time_ms : Double) -> Unit {
self.state.advance_time(time_ms)
}
///|
pub fn AppRuntime::destroy(self : AppRuntime) -> Unit {
self.state.request_destroy(self.destroy_program_runtime_fn.val)
}
///|
pub fn AppRuntime::layout(self : AppRuntime) -> @core.LayoutResult {
self.state.layout()
}
///|
pub fn AppRuntime::read_semantics(
self : AppRuntime,
since? : @core.SemanticsGeneration,
) -> Result[SemanticsRead, SemanticsReadError] {
self.state.read_semantics(since)
}
///|
pub fn AppRuntime::perform_semantics_action(
self : AppRuntime,
request : PerformSemanticsActionRequest,
) -> Result[SemanticsActionReceipt, SemanticsActionDispatchError] {
match self.state.perform_semantics_action(request) {
Ok(receipt) => {
let program = self.program_runtime_snapshot()
Ok({
..receipt,
pending_work: receipt.pending_work ||
program.pending_message_count > 0 ||
program.active_effect_task_count > 0,
})
}
Err(error) => Err(error)
}
}
///|
pub fn AppRuntime::focused_text_input(
self : AppRuntime,
) -> @core.TextInputState {
self.state.focused_text_input()
}
///|
pub fn AppRuntime::focus_next(self : AppRuntime) -> Bool {
self.state.move_focus()
}
///|
pub fn AppRuntime::focus_previous(self : AppRuntime) -> Bool {
self.state.move_focus(reverse=true)
}
///|
pub fn AppRuntime::focus_key(self : AppRuntime, key : String) -> Bool {
self.state.focus_key(key)
}
///|
pub fn AppRuntime::draw_commands(self : AppRuntime) -> Array[@core.DrawCommand] {
self.state.build_draw_commands()
}
///|
pub fn AppRuntime::draw_frame(self : AppRuntime) -> @core.DrawFrame {
self.state.build_draw_frame()
}
///|
pub fn AppRuntime::size(self : AppRuntime) -> @core.Size {
self.state.size
}
///|
pub fn AppRuntime::needs_redraw(self : AppRuntime) -> Bool {
self.state.dirty.needs_redraw
}
///|
pub fn AppRuntime::needs_rebuild(self : AppRuntime) -> Bool {
self.state.dirty.needs_rebuild
}
///|
pub fn AppRuntime::needs_layout(self : AppRuntime) -> Bool {
self.state.dirty.needs_layout
}
///|
pub fn AppRuntime::needs_paint(self : AppRuntime) -> Bool {
self.state.dirty.needs_paint
}
///|
pub fn AppRuntime::dirty_count(self : AppRuntime) -> Int {
self.state.dirty_count()
}
///|
pub fn AppRuntime::dirty_element_ids(self : AppRuntime) -> Array[Int] {
self.state.dirty_element_ids().map(id => id.value())
}
///|
pub fn AppRuntime::inspector_snapshot(self : AppRuntime) -> InspectorSnapshot {
self.inspector_snapshot_from_state()
}
///|
pub fn AppRuntime::program_runtime_snapshot(
self : AppRuntime,
) -> ProgramRuntimeSnapshot {
(self.program_runtime_snapshot_fn)()
}
///|
pub fn AppRuntime::set_redraw_callback(
self : AppRuntime,
callback : () -> Unit,
) -> Unit {
self.state.set_redraw_callback(callback)
}
///|
/// Installs an observer invoked with every `AppEvent` before it is processed.
/// Replaces any previously installed observer. Passing a no-op effectively
/// disables observation. This is the additive hook the agent layer consumes;
/// it is inert unless explicitly called.
pub fn AppRuntime::set_event_observer(
self : AppRuntime,
observer : (@core.AppEvent) -> Unit,
) -> Unit {
self.state.event_observer = observer
}
///|
pub fn AppRuntime::action_commands(
self : AppRuntime,
) -> Array[@core.ActionCommand] {
self.state.action_commands()
}
///|
pub fn AppRuntime::dispatch_command(
self : AppRuntime,
intent : @core.CommandIntent,
) -> Bool {
self.state.dispatch_command(intent)
}
///|
pub fn AppRuntime::dispatch_action_command(
self : AppRuntime,
command : @core.ActionCommand,
) -> Bool {
self.state.dispatch_action_command(command)
}
///|
pub fn AppRuntime::dispatch_focused_text_command(
self : AppRuntime,
intent : @core.CommandIntent,
paste_text? : String? = None,
) -> @core.TextCommandResult {
self.state.dispatch_focused_text_command(intent, paste_text~)
}