//! Allocate and connect the managed desktop, type tables and host globals. use super::*; pub fn install(vm: &mut Vm, roots: &mut Vec) -> Result { let desktop = vm.new_object()?; roots.push(desktop); vm.set_attr(desktop, "viewport", Value::Nil)?; let tabs = vm.new_dict()?; roots.push(tabs); vm.set_attr(desktop, "tabs", Value::Object(tabs))?; let ui = vm.new_object()?; roots.push(ui); let session = Arc::new(Mutex::new(Session { nodes: Vec::new(), presentations: std::collections::BTreeMap::from([( crate::window::MAIN_WINDOW, crate::presentation::Presentation::new(desktop, tabs), )]), current: crate::window::MAIN_WINDOW, master: desktop, next_window: crate::window::MAIN_WINDOW, next_mount: 0, retired_commands: Vec::new(), refresh_yields: 0, refresh_restarts: 0, layout_cancels: 0, paint_cancels: 0, paint_slices: 0, binding_evaluations: 0, exiting: false, })); let mut public_functions = Vec::new(); let mut factories = Vec::new(); for (name, operation) in [ ("implement", Operation::Implement), ("implements", Operation::Implements), ("supports", Operation::Supports), ("isInstance", Operation::IsInstance), ("isSubtype", Operation::IsSubtype), ("open", Operation::OpenValue), ("text", Operation::Text), ("rect", Operation::Rect), ("spacer", Operation::Spacer), ("row", Operation::Row), ("column", Operation::Column), ("button", Operation::Button), ("page", Operation::Page), ("present", Operation::Present), ("state", Operation::State), ("textOf", Operation::TextOf), ("stats", Operation::Stats), ("viewport", Operation::Viewport), ("window", Operation::Window), ] { let function = vm.allocate(ContextNativeFunction::new( name, Function { signature: None, operation, owner: None, session: session.clone(), requested_type: None, }, vec![desktop], ))?; roots.push(function); vm.set_attr(ui, name, Value::Object(function))?; public_functions.push((name, function, operation)); } let root_types = vm.bootstrap_types()?; roots.extend([root_types.object, root_types.type_type]); vm.set_attr(ui, "Object", Value::Object(root_types.object))?; vm.set_attr(ui, "Type", Value::Object(root_types.type_type))?; let mut definitions = vec![ ("Object", root_types.object), ("Type", root_types.type_type), ]; for (name, base_name, constructor_name) in [ ("Trait", "Object", None), ("Function", "Object", None), ("Block", "Object", None), ("ContainerBlock", "Block", None), ("TextBlock", "Block", Some("text")), ("RectBlock", "Block", Some("rect")), ("Spacer", "Block", Some("spacer")), ("ScrollBlock", "ContainerBlock", Some("scroll")), ("Row", "ContainerBlock", Some("row")), ("Column", "ContainerBlock", Some("column")), ("Button", "ContainerBlock", Some("button")), ("Page", "ContainerBlock", Some("page")), ("Window", "Object", Some("window-create")), ("Mount", "Object", None), ("Event", "Object", Some("event")), ("Subscription", "Object", None), ("Task", "Object", None), ("ObjectList", "Object", Some("object-list")), ] { let base = object(Some(&vm.get_attr(ui, base_name)?))?; let constructor = match constructor_name { Some("object-list") => { let factory = vm.allocate(ContextNativeFunction::new( "object-list", crate::object_list::Constructor { desktop, signature: None, }, vec![desktop], ))?; roots.push(factory); let function_type = object(Some(&vm.get_attr(ui, "Function")?))?; vm.attach_type(factory, function_type)?; TypeConstructor::NativeTyped(factory) } Some("event") => { let factory = vm.allocate(ContextNativeFunction::new( "event", crate::event_dispatch::Constructor { signature: None }, vec![], ))?; roots.push(factory); let function_type = object(Some(&vm.get_attr(ui, "Function")?))?; vm.attach_type(factory, function_type)?; TypeConstructor::NativeTyped(factory) } Some(constructor_name) => { let operation = match constructor_name { "text" => Operation::Text, "rect" => Operation::Rect, "spacer" => Operation::Spacer, "scroll" => Operation::Scroll, "row" => Operation::Row, "column" => Operation::Column, "button" => Operation::Button, "page" => Operation::Page, "window-create" => Operation::NewWindow, _ => return Err(invalid("unknown native type allocator").into()), }; let factory = vm.allocate(ContextNativeFunction::new( constructor_name, NativeFactory { signature: None, options: None, operation, session: session.clone(), }, vec![desktop], ))?; roots.push(factory); let function_type = object(Some(&vm.get_attr(ui, "Function")?))?; vm.attach_type(factory, function_type)?; TypeConstructor::NativeTyped(factory) } None => TypeConstructor::Abstract, }; if let (Some(tag), TypeConstructor::NativeTyped(factory)) = (constructor_name, constructor) { factories.push((tag, factory)); } let definition = vm.new_type(name, base, constructor)?; roots.push(definition); vm.set_attr(ui, name, Value::Object(definition))?; definitions.push((name, definition)); } for (name, fields) in [ ( "Constraints", vec!["minWidth", "minHeight", "maxWidth", "maxHeight"], ), ("MeasureResult", vec!["width", "height"]), ("Bounds", vec!["x", "y", "width", "height"]), ("Point", vec!["x", "y"]), ("Arrangement", vec!["children"]), ("DisplayList", vec!["commands"]), ("PaintCommand", vec![]), ] { let fields = fields .into_iter() .map(|name| endor_core::ClassField { name: name.into(), type_name: if matches!(name, "children" | "commands") { "Object" } else { "Number" } .into(), }) .collect(); let definition = vm.new_class( name, root_types.object, fields, std::collections::BTreeMap::new(), )?; roots.push(definition); vm.set_attr(ui, name, Value::Object(definition))?; definitions.push((name, definition)); } // The desktop owns the registry through ordinary traced attributes. The // Rust session only borrows handles and adds no hidden lifetime roots. let types = vm.new_dict()?; roots.push(types); for (name, definition) in definitions { vm.dict_set(types, Value::Text(name.into()), Value::Object(definition))?; } vm.set_attr(desktop, "types", Value::Object(types))?; vm.attach_type(desktop, root_types.object)?; vm.attach_type(ui, root_types.object)?; let function_type = object(Some(&vm.get_attr(ui, "Function")?))?; vm.set_function_type(function_type)?; vm.set_source_type_checks(true)?; for (name, function, operation) in public_functions { vm.replace_payload( function, ContextNativeFunction::new( name, Function { signature: Some(operation_signature(vm.object_store(), desktop, operation)?), operation, owner: None, session: session.clone(), requested_type: None, }, vec![desktop], ), )?; } for (name, function) in factories { let operation = match name { "text" => Some(Operation::Text), "rect" => Some(Operation::Rect), "spacer" => Some(Operation::Spacer), "scroll" => Some(Operation::Scroll), "row" => Some(Operation::Row), "column" => Some(Operation::Column), "button" => Some(Operation::Button), "page" => Some(Operation::Page), "window-create" => Some(Operation::NewWindow), "event" | "object-list" => None, _ => return Err(invalid("unknown deferred factory").into()), }; if let Some(operation) = operation { let options = operation_signature(vm.object_store(), desktop, operation)?; let envelope = CallSignature { arity: endor_core::CallArity::range(options.arity.required + 1, usize::MAX), parameters: vec![AnnotationType::Type.into()], variadic: Some(AnnotationType::Any.into()), returns: options.returns.clone(), effect: options.effect, }; let mut captures = vec![desktop]; captures.extend(options.references()); vm.replace_payload( function, ContextNativeFunction::new( name, NativeFactory { signature: Some(envelope), options: Some(options), operation, session: session.clone(), }, captures, ), )?; } else { let kind = if name == "event" { "Event" } else { "ObjectList" }; let signature = CallSignature { arity: endor_core::CallArity::range( if name == "event" { 2 } else { 1 }, usize::MAX, ), parameters: if name == "event" { vec![AnnotationType::Type.into(), AnnotationType::String.into()] } else { vec![AnnotationType::Type.into()] }, variadic: Some(AnnotationType::Any.into()), returns: AnnotationType::Nominal(desktop_type(vm.object_store(), kind, desktop)?) .into(), effect: CallEffect::ReadOnly, }; if name == "event" { vm.replace_payload( function, ContextNativeFunction::new( name, crate::event_dispatch::Constructor { signature: Some(signature), }, vec![], ), )?; } else { vm.replace_payload( function, ContextNativeFunction::new( name, crate::object_list::Constructor { desktop, signature: Some(signature), }, vec![desktop], ), )?; } } } for name in [ "open", "text", "rect", "spacer", "row", "column", "button", "page", "present", "state", "textOf", "stats", "viewport", "window", "isInstance", "isSubtype", "implement", "implements", "supports", ] { let function = object(Some(&vm.get_attr(ui, name)?))?; vm.attach_type(function, function_type)?; } for (type_name, name, operation) in [ ("Block", "bind", Operation::Bind), ("Block", "mounts", Operation::Mounts), ("Block", "invalidate", Operation::Invalidate), ("Block", "onClick", Operation::OnClick), ("Block", "on", Operation::On), ("Block", "dispatch", Operation::Dispatch), ("Block", "onEvent", Operation::OnEvent), ("Block", "onMount", Operation::OnMount), ("Block", "onUnmount", Operation::OnUnmount), ("Block", "click", Operation::Click), ("TextBlock", "setText", Operation::SetText), ("TextBlock", "setColor", Operation::SetColor), ("RectBlock", "setColor", Operation::SetColor), ("ContainerBlock", "append", Operation::Append), ("ContainerBlock", "insert", Operation::Insert), ("ContainerBlock", "remove", Operation::Remove), ("ContainerBlock", "replace", Operation::Replace), ("ContainerBlock", "move", Operation::Move), ("ContainerBlock", "children", Operation::Children), ("Window", "close", Operation::CloseWindow), ("Page", "moveToWindow", Operation::MoveWindow), ("Page", "moveTab", Operation::MoveTab), ("Page", "detachToWindow", Operation::Detach), ("Page", "render", Operation::Render), ("Page", "unmount", Operation::Unmount), ("Page", "open", Operation::Open), ("Page", "window", Operation::PageWindow), ("Page", "close", Operation::Close), ("Page", "activate", Operation::Activate), ("Page", "navigate", Operation::Navigate), ("Page", "back", Operation::Back), ("Page", "forward", Operation::Forward), ] { let expected = object(Some(&vm.get_attr(ui, type_name)?))?; let method = vm.allocate(ContextNativeFunction::new( name, TypeMethod { signature: method_signature(vm.object_store(), desktop, expected, operation)?, operation, expected, session: session.clone(), }, vec![expected, desktop], ))?; roots.push(method); vm.attach_type(method, function_type)?; vm.define_method(expected, name, method)?; } crate::window::install(vm, roots, desktop, tabs)?; let block_type = object(Some(&vm.get_attr(ui, "Block")?))?; crate::native_visual::install(vm, roots, desktop, block_type, function_type)?; let event_type = object(Some(&vm.get_attr(ui, "Event")?))?; let token_type = object(Some(&vm.get_attr(ui, "Subscription")?))?; crate::event_dispatch::install(vm, roots, event_type, token_type, function_type)?; crate::protocols::install(vm, roots, desktop, ui)?; let tasks = crate::tasks::install(vm, roots, ui, desktop)?; developer_pages::install(vm, ui, &session)?; let bridge = UiBridge { ui, session, tasks }; developer_pages::install_start_page(vm, &bridge)?; Ok(bridge) }