Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamic type registration #5

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions examples/text.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// It displays the current FPS in the top left corner, as well as text that changes color
// in the bottom right. For text within a scene, please see the text2d example.

const { Entity, World } = bevyEcs;
const { Entity, World, TypeRegistry, ReflectableObject } = bevyEcs;
const { Visibility, ComputedVisibility } = bevyRender.view.visibility;
const { FocusPolicy } = bevyUi.focus;
const { Node, CalculatedSize, Style, AlignSelf, PositionType, Val } =
Expand All @@ -23,8 +23,18 @@ const {
const { Color } = bevyRender.color;
const { Vec } = alloc.vec;

class ColorText extends ReflectableObject {
constructor() {
super({});
}
typeName() {
return "bevy_js::text::ColorText";
}
}

(async () => {
await bevyEcs.waitForWorld();
TypeRegistry.register(new ColorText());
setup();
})();

Expand Down Expand Up @@ -77,5 +87,6 @@ function setup() {
is_visible_in_hierarchy: false,
is_visible_in_view: false,
})
);
)
.insert(new ColorText());
}
2 changes: 1 addition & 1 deletion src/runtimes/bevy/ext/bevy_ecs/03_ecs.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
try {
return reflectable.reflect();
} catch (err) {
throw new Error(`Object must implement method "reflect" in order to be reflected:
throw new Error(`Object must implement method "reflect" in order to be registered:
${err}`);
}
}
Expand Down
20 changes: 20 additions & 0 deletions src/runtimes/bevy/ext/bevy_ecs/04_reflect.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
"use strict";

((window) => {
const { core } = window.Deno;
const { worldResourceId, reflect } = window.bevyEcs;

class Reflectable {
constructor() {}

Expand Down Expand Up @@ -75,11 +78,28 @@
}
}

class TypeRegistry {
static register(reflectable) {
const reflected = reflect(reflectable);

try {
core.ops.op_type_registry_register(worldResourceId(), reflected);
} catch (err) {
throw new Error(
`Could not register type:
${JSON.stringify(reflected)}
${err}`
);
}
}
}

Object.assign(window.bevyEcs, {
Reflectable,
ReflectableObject,
ReflectableArray,
ReflectableEnum,
ReflectableUnit,
TypeRegistry,
});
})(globalThis);
3 changes: 3 additions & 0 deletions src/runtimes/bevy/ext/bevy_ecs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ use crate as bjs;
use bjs::{include_js_files, op, Extension, OpState};

mod entity;
mod reflect;
pub mod serde;

pub fn init() -> Extension {
Extension::builder()
Expand All @@ -14,6 +16,7 @@ pub fn init() -> Extension {
))
.ops(vec![
op_wait_for_world::decl(),
reflect::op_type_registry_register::decl(),
entity::op_entity_spawn::decl(),
entity::op_entity_insert_component::decl(),
])
Expand Down
34 changes: 34 additions & 0 deletions src/runtimes/bevy/ext/bevy_ecs/reflect.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use crate::{self as bjs};
use dc::{anyhow::Error as AnyError, op, serde::de::DeserializeSeed, serde_v8, OpState};
use deno_core as dc;

use super::serde::de::DynamicReflectDeserializer;

#[op(v8)]
pub fn op_type_registry_register<'scope>(
scope: &mut deno_core::v8::HandleScope<'scope>,
state: &mut OpState,
r_world: bjs::ResourceId,
reflectable: serde_v8::Value,
) -> Result<(), AnyError> {
let res = bjs::runtimes::unwrap_world_resource(state, r_world);
let _world = res.borrow_world_mut();

// Deserialize reflectable value into a dynamic type

let mut value_deserializer = serde_v8::Deserializer::new(scope, reflectable.v8_value, None);

let mut track = serde_path_to_error::Track::new();
let tracked = serde_path_to_error::Deserializer::new(&mut value_deserializer, &mut track);

let reflected = DynamicReflectDeserializer
.deserialize(tracked)
.map_err(|err| AnyError::msg(format!("{}, occured at: {}", err, track.path())))?;

dbg!(&reflected);
dbg!(reflected.get_type_info());

// TODO: How to get [TypeRegistration] from [dyn Reflect]?

Ok(())
}
157 changes: 157 additions & 0 deletions src/runtimes/bevy/ext/bevy_ecs/serde/de.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
use bevy::reflect::{DynamicList, DynamicStruct, Reflect};
use deno_core::serde::{
self,
de::{DeserializeSeed, Error, MapAccess, SeqAccess, Visitor},
};
use std::fmt;

macro_rules! deserialize_reflect {
( $dmethod:ident, $t:tt ) => {
fn $dmethod<E>(self, v: $t) -> Result<Self::Value, E>
where
E: Error,
{
Ok(Box::new(v))
}
};
}

pub struct DynamicReflectDeserializer;

impl<'de> DeserializeSeed<'de> for DynamicReflectDeserializer {
type Value = Box<dyn Reflect>;

fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_any(DynamicReflectDeserializerVisitor)
}
}

struct DynamicReflectDeserializerVisitor;

impl<'de> Visitor<'de> for DynamicReflectDeserializerVisitor {
type Value = Box<dyn Reflect>;

fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("map containing a `type` entry containing `value` or a value type")
}

deserialize_reflect!(visit_bool, bool);

deserialize_reflect!(visit_i8, i8);
deserialize_reflect!(visit_i16, i16);
deserialize_reflect!(visit_i32, i32);
deserialize_reflect!(visit_i64, i64);

deserialize_reflect!(visit_u8, u8);
deserialize_reflect!(visit_u16, u16);
deserialize_reflect!(visit_u32, u32);
deserialize_reflect!(visit_u64, u64);

deserialize_reflect!(visit_f32, f32);
deserialize_reflect!(visit_f64, f64);

deserialize_reflect!(visit_string, String);

fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: Error,
{
Ok(Box::new(v.to_string()))
}

fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: MapAccess<'de>,
{
let type_name = map
.next_key::<String>()?
.ok_or_else(|| Error::invalid_length(0, &"at least one entry"))?;

map.next_value_seed(TypedDynamicReflectDeserializer { type_name })
}
}

pub struct TypedDynamicReflectDeserializer {
type_name: String,
}

impl<'de> DeserializeSeed<'de> for TypedDynamicReflectDeserializer {
type Value = Box<dyn Reflect>;

fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_any(TypedDynamicReflectDeserializerVisitor {
type_name: self.type_name,
})
}
}

struct TypedDynamicReflectDeserializerVisitor {
type_name: String,
}

impl<'de> Visitor<'de> for TypedDynamicReflectDeserializerVisitor {
type Value = Box<dyn Reflect>;

fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter
.write_str("object that can be represented as dynamic type or reflected as a value")
}

deserialize_reflect!(visit_bool, bool);

deserialize_reflect!(visit_i8, i8);
deserialize_reflect!(visit_i16, i16);
deserialize_reflect!(visit_i32, i32);
deserialize_reflect!(visit_i64, i64);

deserialize_reflect!(visit_u8, u8);
deserialize_reflect!(visit_u16, u16);
deserialize_reflect!(visit_u32, u32);
deserialize_reflect!(visit_u64, u64);

deserialize_reflect!(visit_f32, f32);
deserialize_reflect!(visit_f64, f64);

deserialize_reflect!(visit_string, String);

fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: Error,
{
Ok(Box::new(v.to_string()))
}

fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
let mut list = DynamicList::default();
list.set_name(self.type_name);

while let Some(value) = seq.next_element_seed(DynamicReflectDeserializer)? {
list.push_box(value);
}
Ok(Box::new(list))
}

fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: MapAccess<'de>,
{
let mut dynamic_struct = DynamicStruct::default();
dynamic_struct.set_name(self.type_name);

while let Some(key) = map.next_key::<String>()? {
let value = map.next_value_seed(DynamicReflectDeserializer)?;
dynamic_struct.insert_boxed(&key, value);
}

Ok(Box::new(dynamic_struct))
}
}
1 change: 1 addition & 0 deletions src/runtimes/bevy/ext/bevy_ecs/serde/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod de;