Files
endorphin-tessera-gui/src/bridge/ui_bridge/bootstrap.rs
T

451 lines
18 KiB
Rust

//! 安装一次 VM 域需要的宿主类型、对象和方法。类型注册表是源代码调用原生能力的入口。
//! 先建立类型与桌面基础设施,再绑定构造器、视觉协议和开发工具工厂;返回的根负责保持这些对象存活。
//!
//! Allocate and connect the managed desktop, type tables and host globals.
use super::*;
/// 把本模块的类型、方法或能力注册到所属运行时上下文。
pub fn install(vm: &mut Vm, roots: &mut Vec<ObjectRef>) -> Result<UiBridge> {
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),
("overlay", Operation::Overlay),
("scene2D", Operation::Scene2D),
("canvasViewport", Operation::CanvasViewport),
("camera2D", Operation::Camera2D),
("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")),
("ScrollView", "ScrollBlock", Some("scroll")),
("Row", "ContainerBlock", Some("row")),
("Column", "ContainerBlock", Some("column")),
("Overlay", "ContainerBlock", Some("overlay")),
("Scene2D", "ContainerBlock", Some("scene2D")),
("CanvasViewport", "ContainerBlock", Some("canvasViewport")),
("Camera2D", "Object", Some("camera2D")),
("Vec3", "Object", Some("vec3")),
("Triangle3D", "Object", Some("triangle3D")),
("Geometry3D", "Object", Some("geometry3D")),
("Mesh3D", "Object", Some("mesh3D")),
("Box3D", "Mesh3D", Some("box3D")),
("Group3D", "Object", Some("group3D")),
("Scene3D", "Object", Some("scene3D")),
("DirectionalLight", "Object", Some("directionalLight")),
("PerspectiveCamera", "Object", Some("perspectiveCamera")),
("OrthographicCamera", "Object", Some("orthographicCamera")),
("OrbitController", "Object", Some("orbitController")),
("Viewport3D", "Block", Some("viewport3D")),
("Panel3D", "Object", Some("panel3D")),
("RayHit3D", "Object", None),
("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,
"overlay" => Operation::Overlay,
"scene2D" => Operation::Scene2D,
"canvasViewport" => Operation::CanvasViewport,
"camera2D" => Operation::Camera2D,
"viewport3D" => Operation::Viewport3D,
"button" => Operation::Button,
"page" => Operation::Page,
"window-create" => Operation::NewWindow,
name => crate::scene3d_model::Constructor::from_function(name)
.map(Operation::SceneObject)
.ok_or(invalid("unknown native type allocator"))?,
};
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![]),
("CameraGesture", 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),
"overlay" => Some(Operation::Overlay),
"scene2D" => Some(Operation::Scene2D),
"canvasViewport" => Some(Operation::CanvasViewport),
"camera2D" => Some(Operation::Camera2D),
"viewport3D" => Some(Operation::Viewport3D),
"button" => Some(Operation::Button),
"page" => Some(Operation::Page),
"window-create" => Some(Operation::NewWindow),
"event" | "object-list" => None,
name => Some(
crate::scene3d_model::Constructor::from_function(name)
.map(Operation::SceneObject)
.ok_or(invalid("unknown deferred factory"))?,
),
};
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),
("OrbitController", "update", Operation::CameraControl),
("Mesh3D", "translate", Operation::Translate3D),
("Group3D", "translate", Operation::Translate3D),
("Panel3D", "translate", Operation::Translate3D),
("PerspectiveCamera", "translate", Operation::Translate3D),
("OrthographicCamera", "translate", Operation::Translate3D),
("Mesh3D", "rotate", Operation::Rotate3D),
("Group3D", "rotate", Operation::Rotate3D),
("Panel3D", "rotate", Operation::Rotate3D),
("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)
}