Merge branch 'main' of https://github.com/storytold/storyteller-bevy into animation-clips-setup

This commit is contained in:
Lee-Orr
2024-03-11 13:18:27 -04:00
10 changed files with 618 additions and 138 deletions
+5
View File
@@ -21,6 +21,11 @@ frontend project, run:
npx nx serve studio-frontend
```
You'll need to install the wasm compiler for Rust:
```
rustup target add wasm32-unknown-unknown
```
Projects are located in directories at the workspace root. The list of commands
supported by a given project can be found under the `target` property of its
`project.json` file, which defines the Nx configuration for that project.
+14
View File
@@ -30,4 +30,18 @@ describe("@on(eventName)", () => {
expect(counter.count).toBe(3);
});
it("exhibits correct `this` handling when multiple instances are present", () => {
const counter1 = document.createElement("test-counter");
document.body.appendChild(counter1);
const counter2 = document.createElement("test-counter");
document.body.appendChild(counter2);
counter1.click();
counter2.click();
expect(counter1.count).toBe(1);
expect(counter2.count).toBe(1);
});
});
+23 -5
View File
@@ -15,7 +15,7 @@ export function on(eventSelector: string) {
? eventSelector.split(":")
: [null, eventSelector];
let target = match (targetName, {
const target = match (targetName, {
"window": () => window as EventTarget,
"document": () => document as EventTarget,
_: () => null,
@@ -23,17 +23,35 @@ export function on(eventSelector: string) {
return <T extends LitElement>(proto: T, propName: keyof T, desc: PropertyDescriptor) => {
const handler = getMethodDescriptor(proto, propName)?.value ?? (() => {});
const $handler = Symbol(String(propName));
type Decorated = T & {
[$handler]: typeof handler;
}
prependRoutine(proto, "connectedCallback", function (this: T) {
target ??= this;
target.addEventListener(eventName, handler.bind(this));
assertType<Decorated>(this);
this[$handler] ??= handler.bind(this);
if (target) {
target.addEventListener(eventName, this[$handler]);
} else {
this.addEventListener(eventName, this[$handler]);
}
});
prependRoutine(proto, "disconnectedCallback", function (this: T) {
target ??= this;
target.removeEventListener(eventName, handler.bind(this));
assertType<Decorated>(this);
if (target) {
target.removeEventListener(eventName, this[$handler]);
} else {
this.removeEventListener(eventName, this[$handler]);
}
});
return desc;
}
}
function assertType<T>(value: unknown): asserts value is T {}
+36 -35
View File
@@ -1,4 +1,4 @@
import { Ctor, Fn, Method } from "@storyteller/utility";
import { Ctor, Fn } from "@storyteller/utility";
import { LitElement } from "lit";
import { appendRoutine, prependRoutine } from "../decorators/internal";
@@ -7,6 +7,7 @@ import { Token } from "./types";
const $injector = Symbol("injector");
const $$injector = Symbol("#injector");
const $fulfillInjectionRequest = Symbol("fulfillInjectionRequest");
export type ProviderOptions<T, E extends LitElement>
= Token<T>
@@ -41,6 +42,7 @@ export function provide<T, E extends LitElement>(options: ProviderOptions<T, E>)
type Decorated = E & {
[$$injector]?: Map<Token<any>, any> | undefined;
readonly [$injector]: Map<Token<any>, any>;
[$fulfillInjectionRequest](event: InjectionRequest<T>): void;
}
return (Target: Ctor<E>) => {
@@ -48,9 +50,7 @@ export function provide<T, E extends LitElement>(options: ProviderOptions<T, E>)
assertType<Decorated>(proto);
let descriptor = Object.getOwnPropertyDescriptor(proto, $injector);
let needsInit = false;
if (!($injector in proto) || !descriptor) {
needsInit = true;
Object.defineProperty(proto, $injector, {
get(this: Decorated) { return this[$$injector] ??= new Map() },
configurable: false,
@@ -73,17 +73,16 @@ export function provide<T, E extends LitElement>(options: ProviderOptions<T, E>)
this[$injector].set(token, value);
this.dispatchEvent(new DependencyProvision(token, value));
if (needsInit) {
const fulfill = fulfillInjectionRequest.bind(this);
this.addEventListener(DIEvent.InjectionRequest, fulfill as Method<HTMLElement>);
}
this[$fulfillInjectionRequest] ??= fulfillInjectionRequest.bind(this);
this.addEventListener(DIEvent.InjectionRequest, this[$fulfillInjectionRequest]);
});
appendRoutine(proto, "disconnectedCallback", function (this: Decorated): void {
prependRoutine(proto, "disconnectedCallback", function (this: Decorated): void {
const token = "token" in options ? options.token : options;
const value = this[$injector].get(token);
this.dispatchEvent(new ProviderRemoval(token, value));
this.removeEventListener(DIEvent.InjectionRequest, this[$fulfillInjectionRequest]);
});
}
}
@@ -140,43 +139,45 @@ export function inject<T, E extends LitElement>(token: Token<T>) {
*/
export function queryProviders<T, E extends LitElement>(token: Token<T>) {
return <K extends keyof E>(proto: E, key: K) => {
function onProvided(this: E, event: DependencyProvision<T>): void {
const array = this[key] ?? [];
assertType<E[K] & T[]>(array);
const $onProvided = Symbol(`onProvided:${String(key)}`);
const $onRemoved = Symbol(`onRemoved:${String(key)}`);
if (event.detail.token === token)
this[key] = array.concat(event.detail.value) as E[K];
type Decorated = E & {
[$onProvided](event: DependencyProvision<T>): void;
[$onRemoved](event: ProviderRemoval<T>): void;
}
function onRemoved(this: E, event: ProviderRemoval<T>): void {
assertType<Decorated>(proto);
function onProvided(this: Decorated, event: DependencyProvision<T>): void {
const array = this[key] ?? [];
assertType<E[K] & T[]>(array);
assertType<Decorated[K] & T[]>(array);
if (event.detail.token === token)
this[key] = array.filter(el => el !== event.detail.value) as E[K];
this[key] = array.concat(event.detail.value) as Decorated[K];
}
appendRoutine(proto, "connectedCallback", function (this: E): void {
this[key] = [] as E[K];
this.addEventListener(
DIEvent.DependencyProvision,
onProvided.bind(this) as Method<HTMLElement>,
);
this.addEventListener(
DIEvent.ProviderRemoval,
onRemoved.bind(this) as Method<HTMLElement>,
);
function onRemoved(this: Decorated, event: ProviderRemoval<T>): void {
const array = this[key] ?? [];
assertType<Decorated[K] & T[]>(array);
if (event.detail.token === token)
this[key] = array.filter(el => el !== event.detail.value) as Decorated[K];
}
appendRoutine(proto, "connectedCallback", function (this: Decorated): void {
this[key] = [] as Decorated[K];
this[$onProvided] ??= onProvided.bind(this);
this[$onRemoved] ??= onRemoved.bind(this);
this.addEventListener(DIEvent.DependencyProvision, this[$onProvided]);
this.addEventListener(DIEvent.ProviderRemoval, this[$onRemoved]);
});
appendRoutine(proto, "disconnectedCallback", function (this: E): void {
this.removeEventListener(
DIEvent.DependencyProvision,
onProvided.bind(this) as Method<HTMLElement>,
);
this.removeEventListener(
DIEvent.ProviderRemoval,
onRemoved.bind(this) as Method<HTMLElement>,
);
appendRoutine(proto, "disconnectedCallback", function (this: Decorated): void {
this.removeEventListener(DIEvent.DependencyProvision, this[$onProvided]);
this.removeEventListener(DIEvent.ProviderRemoval, this[$onRemoved]);
});
}
}
+250
View File
@@ -0,0 +1,250 @@
import { LitElement, html, render } from "lit";
import { customElement, property } from "lit/decorators.js";
import { createRef, ref } from "lit/directives/ref.js";
import { provide, inject, queryProviders } from "./decorators";
import { UniqueToken } from "./types";
const TEST_TOKEN = UniqueToken.create<HTMLElement>();
@customElement("test-token-provider")
@provide(TEST_TOKEN)
class TestTokenProvider extends LitElement {
override render = () => html`<slot></slot>`
}
@customElement("test-concrete-provider")
@provide(TestConcreteProvider)
class TestConcreteProvider extends LitElement {
override render = () => html`<slot></slot>`
}
const TEST_VALUE = UniqueToken.create<number>();
@customElement("test-value-provider")
@provide({
token: TEST_VALUE,
provide() { return this.value; }
})
class TestValueProvider extends LitElement {
@property({ type: Number })
value = 42;
override render = () => html`<slot></slot>`
}
@customElement("test-injector")
class TestInjector extends LitElement {
@inject(TEST_TOKEN)
tokenProvider: unknown;
@inject(TestConcreteProvider)
concreteProvider?: TestConcreteProvider;
@inject(TEST_VALUE)
valueProvider?: number;
override render = () => html`<slot></slot>`;
}
@customElement("test-queryer")
class TestQueryer extends LitElement {
@queryProviders(TEST_TOKEN)
tokenProviders: unknown[] = [];
@queryProviders(TestConcreteProvider)
concreteProviders: TestConcreteProvider[] = [];
@queryProviders(TEST_VALUE)
valueProviders: number[] = [];
override render = () => html`<slot></slot>`
}
describe("`@provide` and `@inject`", () => {
it("works with abstract tokens", () => {
const providerRef = createRef<TestTokenProvider>();
const injectorRef = createRef<TestInjector>();
render(html`
<test-token-provider ${ref(providerRef)}>
<test-injector ${ref(injectorRef)}></test-injector>
</test-token-provider>
`, document.body);
expect(providerRef.value).toBeInstanceOf(TestTokenProvider);
expect(injectorRef.value).toBeInstanceOf(TestInjector);
expect(injectorRef.value!.tokenProvider).toBe(providerRef.value);
});
it("works with concrete tokens", () => {
const providerRef = createRef<TestConcreteProvider>();
const injectorRef = createRef<TestInjector>();
render(html`
<test-concrete-provider ${ref(providerRef)}>
<test-injector ${ref(injectorRef)}></test-injector>
</test-concrete-provider>
`, document.body);
expect(providerRef.value).toBeInstanceOf(TestConcreteProvider);
expect(injectorRef.value).toBeInstanceOf(TestInjector);
expect(injectorRef.value!.concreteProvider).toBe(providerRef.value);
});
it("works with value tokens", () => {
const providerRef = createRef<TestValueProvider>();
const injectorRef = createRef<TestInjector>();
render(html`
<test-value-provider ${ref(providerRef)}>
<test-injector ${ref(injectorRef)}></test-injector>
</test-value-provider>
`, document.body);
expect(providerRef.value).toBeInstanceOf(TestValueProvider);
expect(injectorRef.value).toBeInstanceOf(TestInjector);
expect(injectorRef.value!.valueProvider).toBe(42);
});
it("works with multiple provider/injector instances", () => {
const parentProviderRef = createRef<TestValueProvider>();
const childProviderRef = createRef<TestValueProvider>();
const parentInjectorRef = createRef<TestInjector>();
const childInjectorRef = createRef<TestInjector>();
render(html`
<test-value-provider
${ref(parentProviderRef)}
.value=${420}
>
<test-injector ${ref(parentInjectorRef)}></test-injector>
<test-value-provider
${ref(childProviderRef)}
.value=${69}
>
<test-injector ${ref(childInjectorRef)}></test-injector>
</test-value-provider>
</test-value-provider>
`, document.body);
expect(parentProviderRef.value).toBeInstanceOf(TestValueProvider);
expect(childProviderRef.value).toBeInstanceOf(TestValueProvider);
expect(parentInjectorRef.value).toBeInstanceOf(TestInjector);
expect(childInjectorRef.value).toBeInstanceOf(TestInjector);
expect(parentInjectorRef.value!.valueProvider).toBe(420);
expect(childInjectorRef.value!.valueProvider).toBe(69);
});
it("works with nested providers", () => {
const tokenProviderRef = createRef<TestTokenProvider>();
const concreteProviderRef = createRef<TestConcreteProvider>();
const valueProviderRef = createRef<TestValueProvider>();
const injectorRef = createRef<TestInjector>();
render(html`
<test-token-provider ${ref(tokenProviderRef)}>
<test-concrete-provider ${ref(concreteProviderRef)}>
<test-value-provider ${ref(valueProviderRef)}>
<test-injector ${ref(injectorRef)}></test-injector>
</test-value-provider>
</test-concrete-provider>
</test-token-provider>
`, document.body);
expect(tokenProviderRef.value).toBeInstanceOf(TestTokenProvider);
expect(concreteProviderRef.value).toBeInstanceOf(TestConcreteProvider);
expect(valueProviderRef.value).toBeInstanceOf(TestValueProvider);
expect(injectorRef.value).toBeInstanceOf(TestInjector);
expect(injectorRef.value!.tokenProvider).toBe(tokenProviderRef.value);
expect(injectorRef.value!.concreteProvider).toBe(concreteProviderRef.value);
expect(injectorRef.value!.valueProvider).toBe(42);
});
});
describe("`@provide` and `@queryProviders`", () => {
it("works with abstract tokens", () => {
const queryerRef = createRef<TestQueryer>();
const providerRef = createRef<TestTokenProvider>();
render(html`
<test-queryer ${ref(queryerRef)}>
<test-token-provider ${ref(providerRef)}></test-token-provider>
</test-queryer>
`, document.body);
expect(queryerRef.value).toBeInstanceOf(TestQueryer);
expect(providerRef.value).toBeInstanceOf(TestTokenProvider);
expect(queryerRef.value?.tokenProviders).toEqual([providerRef.value]);
});
it("works with concrete tokens", () => {
const queryerRef = createRef<TestQueryer>();
const providerRef = createRef<TestConcreteProvider>();
render(html`
<test-queryer ${ref(queryerRef)}>
<test-concrete-provider ${ref(providerRef)}></test-concrete-provider>
</test-queryer>
`, document.body);
expect(queryerRef.value).toBeInstanceOf(TestQueryer);
expect(providerRef.value).toBeInstanceOf(TestConcreteProvider);
expect(queryerRef.value?.concreteProviders).toEqual([providerRef.value]);
});
it("works with value tokens", () => {
const queryerRef = createRef<TestQueryer>();
const providerRef = createRef<TestValueProvider>();
render(html`
<test-queryer ${ref(queryerRef)}>
<test-value-provider ${ref(providerRef)}></test-value-provider>
</test-queryer>
`, document.body);
expect(queryerRef.value).toBeInstanceOf(TestQueryer);
expect(providerRef.value).toBeInstanceOf(TestValueProvider);
expect(queryerRef.value?.valueProviders).toEqual([42]);
});
it("works with multiple providers", () => {
const queryerRef = createRef<TestQueryer>();
render(html`
<test-queryer ${ref(queryerRef)}>
<test-value-provider .value=${42}></test-value-provider>
<test-value-provider .value=${420}></test-value-provider>
<test-value-provider .value=${69}></test-value-provider>
</test-queryer>
`, document.body);
expect(queryerRef.value).toBeInstanceOf(TestQueryer);
expect(queryerRef.value!.valueProviders).toEqual([42, 420, 69]);
});
it("dynamically updates", () => {
const queryerRef = createRef<TestQueryer>();
render(html`
<test-queryer ${ref(queryerRef)}>
<test-value-provider .value=${42}></test-value-provider>
<test-value-provider .value=${420}></test-value-provider>
<test-value-provider .value=${69}></test-value-provider>
</test-queryer>
`, document.body);
expect(queryerRef.value).toBeInstanceOf(TestQueryer);
expect(queryerRef.value!.valueProviders).toEqual([42, 420, 69]);
const middleElement = queryerRef.value!.children.item(1) as TestValueProvider;
expect(middleElement).toBeInstanceOf(TestValueProvider);
middleElement.disconnectedCallback();
middleElement.remove();
expect(queryerRef.value!.valueProviders).toEqual([42, 69]);
});
});
+2 -1
View File
@@ -1,5 +1,6 @@
import { type AbstractCtor, type Ctor, type Fn } from "@storyteller/utility";
import { type PropertyValues } from "lit";
import { v4 as uuid } from "uuid";
export class UniqueToken {
get id() { return this.#id; }
@@ -10,7 +11,7 @@ export class UniqueToken {
}
static create<T>(name?: string): Token<T> {
return new UniqueToken(name ?? crypto.randomUUID());
return new UniqueToken(name ?? uuid());
}
toString(): string {
+2 -1
View File
@@ -12,6 +12,7 @@ import { customElement, state } from "lit/decorators.js";
import { classMap } from "lit/directives/class-map.js";
import { createRef, ref } from "lit/directives/ref.js";
import { styleMap } from "lit/directives/style-map.js";
import { v4 as uuid } from "uuid";
import { ImageInfo, LcmClient } from "./lcm-client";
@@ -38,7 +39,7 @@ export class AppElement extends LitElement {
@state() inputUrl?: string;
@state() outputUrl?: string;
#lcmClient = new LcmClient(crypto.randomUUID());
#lcmClient = new LcmClient(uuid());
#outputImageRef = createRef<HTMLImageElement>();
constructor () {
+10
View File
@@ -0,0 +1,10 @@
[[headers]]
for = "/*"
[headers.values]
# NB: This is potentially dangerous, but I don't think we have much other choice:
Access-Control-Allow-Origin = "*"
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-ancestors
# NB: The *.netlify.app is dangerous.
Content-Security-Policy = "frame-ancestors 'self' https://fakeyou.com https://*.fakeyou.com https://storyteller.ai https://*.storyteller.ai https://*.netlify.app http://*.fakeyou.com:7000 http://*.fakeyou.com:7001 http://*.fakeyou.com:7002 http://*.storyteller.ai:7000 http://*.storyteller.ai:7001 http://*.storyteller.ai:7002;"
@@ -4,37 +4,23 @@ import { SceneState, type SceneStateEvent } from "@storyteller/studio";
import { LitElement, nothing } from "lit";
import { customElement } from "lit/decorators.js";
const PARENT_ORIGIN_WHITELIST: readonly string[] = [
"https://fakeyou.com",
"https://storyteller.ai",
"https://staging.fakeyou.com",
"https://staging.storyteller.ai",
"https://feature-mvp--fakeyou.netlify.app",
"https://feature-mvp--storyteller.netlify.app",
"http://dev.fakeyou.com:7000",
"http://dev.fakeyou.com:7001",
"http://dev.fakeyou.com:7002",
"http://dev.storyteller.com:7000",
"http://dev.storyteller.com:7001",
"http://dev.storyteller.com:7002",
"http://localhost:7000",
"http://localhost:7001",
"http://localhost:7002",
];
@customElement("sts-iframe-messaging-bridge")
export class IframeMessagingBridgeElement extends LitElement {
#parentWindow?: Window;
#parentOrigin?: string;
override connectedCallback(): void {
if (
window.parent
&& window.parent !== window
&& PARENT_ORIGIN_WHITELIST.includes(window.parent.origin)
) {
this.#parentWindow = window.parent;
this.#parentOrigin = window.parent.origin;
console.log('window.parent', window.parent);
try {
if (
window.parent
&& window.parent !== window
) {
this.#parentWindow = window.parent;
}
}
catch {
this.#parentWindow = undefined;
}
super.connectedCallback();
@@ -42,8 +28,7 @@ export class IframeMessagingBridgeElement extends LitElement {
@on("window:message")
onParentMessage(event: MessageEvent<string>): void {
if (!this.#parentOrigin || event.origin !== this.#parentOrigin)
return;
console.log('window.parent', window.parent);
if (event.data === "save-scene")
studio.saveScene();
@@ -51,19 +36,23 @@ export class IframeMessagingBridgeElement extends LitElement {
@on("window:scene-uploaded")
onSceneUploaded(event: CustomEvent<string>): void {
if (!this.#parentWindow || !this.#parentOrigin)
console.log('window.parent', window.parent);
if (!this.#parentWindow)
return;
this.#parentWindow.postMessage(`scene-saved:${event.detail}`, this.#parentOrigin);
this.#parentWindow.postMessage(`scene-saved:${event.detail}`, "*");
}
@on("window:scene-state")
onSceneStateChange({ detail: state }: SceneStateEvent): void {
if (!this.#parentWindow || !this.#parentOrigin)
console.log('window.parent', window.parent);
if (!this.#parentWindow)
return;
if (state === SceneState.Active)
this.#parentWindow.postMessage("studio-ready", this.#parentOrigin);
this.#parentWindow.postMessage("studio-ready", "*");
}
protected override render = () => nothing;
+255 -64
View File
@@ -1,6 +1,7 @@
use std::{marker::PhantomData, sync::Arc};
use bevy::{
ecs::world::unsafe_world_cell::UnsafeWorldCell,
prelude::*,
reflect::{serde::ReflectSerializer, GetTypeRegistration},
utils::HashMap,
@@ -60,6 +61,43 @@ impl Plugin for InspectorPlugin {
app.register_inspect::<Transform>();
app.register_inspect::<Name>();
app.register_inspect::<MeshPrimitivePrefab>();
//register standart material
{
app.register_type::<ShortMaterial>()
.add_event::<UpdateInspect<Handle<StandardMaterial>>>()
.add_systems(
Update,
(
update_inspect_by_select_change::<Handle<StandardMaterial>>,
update_inspect_by_remove::<Handle<StandardMaterial>>,
update_inspect_by_change::<Handle<StandardMaterial>>,
notify_asset_change_system::<StandardMaterial>,
)
.in_set(InspectorSystem::DetectChanges),
)
.add_systems(
Update,
serialize_standart_materal.in_set(InspectorSystem::PerComponentCollect),
);
app.world.resource_mut::<InspectGetters>().getters.insert(
StandardMaterial::get_type_registration()
.type_info()
.type_path()
.to_string(),
Arc::new(|entity, path, world, f| unsafe {
let handle = entity.get::<Handle<StandardMaterial>>().unwrap();
let mut assets = world
.get_resource_mut::<Assets<StandardMaterial>>()
.unwrap();
let material = assets.get_mut(handle).unwrap();
let reflect_field = material.reflect_path_mut(path).unwrap();
(f)(reflect_field);
}),
);
}
}
}
@@ -67,12 +105,17 @@ impl Plugin for InspectorPlugin {
struct InspectGetters {
getters: HashMap<
String,
Arc<dyn Fn(&mut EntityWorldMut, &str, &dyn Fn(&mut dyn Reflect)) + Send + Sync>,
Arc<
dyn Fn(&mut EntityWorldMut, &str, &mut UnsafeWorldCell, &dyn Fn(&mut dyn Reflect))
+ Send
+ Sync,
>,
>,
}
trait AppInspectable {
fn register_inspect<T: Component + Reflect + GetTypeRegistration>(&mut self);
fn register_asset_inspect<T: Asset + Reflect + GetTypeRegistration>(&mut self);
}
impl AppInspectable for App {
@@ -100,7 +143,7 @@ impl AppInspectable for App {
.type_info()
.type_path()
.to_string(),
Arc::new(|entity, path, f| {
Arc::new(|entity, path, _, f| {
let mut component = entity.get_mut::<T>().unwrap();
let reflect_field = component.reflect_path_mut(path).unwrap();
(f)(reflect_field);
@@ -108,6 +151,25 @@ impl AppInspectable for App {
);
}
}
fn register_asset_inspect<T: Asset + Reflect + GetTypeRegistration>(&mut self) {
self.register_type::<T>()
.add_event::<UpdateInspect<Handle<T>>>()
.add_systems(
Update,
(
update_inspect_by_select_change::<Handle<T>>,
update_inspect_by_remove::<Handle<T>>,
update_inspect_by_change::<Handle<T>>,
notify_asset_change_system::<T>,
)
.in_set(InspectorSystem::DetectChanges),
)
.add_systems(
Update,
auto_serialize_asset::<T>.in_set(InspectorSystem::PerComponentCollect),
);
}
}
#[derive(Default, Resource)]
@@ -160,7 +222,7 @@ impl<T> Default for UpdateInspect<T> {
fn update_field_by_ui(world: &mut World) {
unsafe {
let unsafe_world = world.as_unsafe_world_cell();
let mut unsafe_world = world.as_unsafe_world_cell();
let Some(msg) = CHANGE_FROM_UI.take_message() else {
return;
};
@@ -181,68 +243,73 @@ fn update_field_by_ui(world: &mut World) {
return;
};
getter.as_ref()(&mut entity_mut, &msg.reflect_path, &|reflect_field| {
if let Some(val) = reflect_field.downcast_mut::<f32>() {
let Ok(msg_value) = serde_json::from_value(msg.value.clone()) else {
error!("Failed to parse value: {}", msg.value);
getter.as_ref()(
&mut entity_mut,
&msg.reflect_path,
&mut unsafe_world,
&|reflect_field| {
if let Some(val) = reflect_field.downcast_mut::<f32>() {
let Ok(msg_value) = serde_json::from_value(msg.value.clone()) else {
error!("Failed to parse value: {}", msg.value);
return;
};
*val = msg_value;
} else if let Some(val) = reflect_field.downcast_mut::<f64>() {
let Ok(msg_value) = serde_json::from_value(msg.value.clone()) else {
error!("Failed to parse value: {}", msg.value);
return;
};
*val = msg_value;
} else if let Some(val) = reflect_field.downcast_mut::<u32>() {
let Ok(msg_value) = serde_json::from_value(msg.value.clone()) else {
error!("Failed to parse value: {}", msg.value);
return;
};
*val = msg_value;
} else if let Some(val) = reflect_field.downcast_mut::<u64>() {
let Ok(msg_value) = serde_json::from_value(msg.value.clone()) else {
error!("Failed to parse value: {}", msg.value);
return;
};
*val = msg_value;
} else if let Some(val) = reflect_field.downcast_mut::<i32>() {
let Ok(msg_value) = serde_json::from_value(msg.value.clone()) else {
error!("Failed to parse value: {}", msg.value);
return;
};
*val = msg_value;
} else if let Some(val) = reflect_field.downcast_mut::<i64>() {
let Ok(msg_value) = serde_json::from_value(msg.value.clone()) else {
error!("Failed to parse value: {}", msg.value);
return;
};
*val = msg_value;
} else if let Some(val) = reflect_field.downcast_mut::<bool>() {
let Ok(msg_value) = serde_json::from_value(msg.value.clone()) else {
error!("Failed to parse value: {}", msg.value);
return;
};
*val = msg_value;
} else if let Some(val) = reflect_field.downcast_mut::<String>() {
*val = msg.value.as_str().unwrap().to_string();
} else if let Some(val) = reflect_field.downcast_mut::<Vec3>() {
let Ok(msg_value) = serde_json::from_value(msg.value.clone()) else {
error!("Failed to parse value: {}", msg.value);
return;
};
*val = msg_value;
} else if let Some(val) = reflect_field.downcast_mut::<Quat>() {
let Ok(msg_value) = serde_json::from_value(msg.value.clone()) else {
error!("Failed to parse value: {}", msg.value);
return;
};
*val = msg_value;
} else {
error!("Unsupported type {}!", &msg.reflect_path);
return;
};
*val = msg_value;
} else if let Some(val) = reflect_field.downcast_mut::<f64>() {
let Ok(msg_value) = serde_json::from_value(msg.value.clone()) else {
error!("Failed to parse value: {}", msg.value);
return;
};
*val = msg_value;
} else if let Some(val) = reflect_field.downcast_mut::<u32>() {
let Ok(msg_value) = serde_json::from_value(msg.value.clone()) else {
error!("Failed to parse value: {}", msg.value);
return;
};
*val = msg_value;
} else if let Some(val) = reflect_field.downcast_mut::<u64>() {
let Ok(msg_value) = serde_json::from_value(msg.value.clone()) else {
error!("Failed to parse value: {}", msg.value);
return;
};
*val = msg_value;
} else if let Some(val) = reflect_field.downcast_mut::<i32>() {
let Ok(msg_value) = serde_json::from_value(msg.value.clone()) else {
error!("Failed to parse value: {}", msg.value);
return;
};
*val = msg_value;
} else if let Some(val) = reflect_field.downcast_mut::<i64>() {
let Ok(msg_value) = serde_json::from_value(msg.value.clone()) else {
error!("Failed to parse value: {}", msg.value);
return;
};
*val = msg_value;
} else if let Some(val) = reflect_field.downcast_mut::<bool>() {
let Ok(msg_value) = serde_json::from_value(msg.value.clone()) else {
error!("Failed to parse value: {}", msg.value);
return;
};
*val = msg_value;
} else if let Some(val) = reflect_field.downcast_mut::<String>() {
*val = msg.value.as_str().unwrap().to_string();
} else if let Some(val) = reflect_field.downcast_mut::<Vec3>() {
let Ok(msg_value) = serde_json::from_value(msg.value.clone()) else {
error!("Failed to parse value: {}", msg.value);
return;
};
*val = msg_value;
} else if let Some(val) = reflect_field.downcast_mut::<Quat>() {
let Ok(msg_value) = serde_json::from_value(msg.value.clone()) else {
error!("Failed to parse value: {}", msg.value);
return;
};
*val = msg_value;
} else {
error!("Unsupported type {}!", &msg.reflect_path);
return;
}
});
}
},
);
}
}
@@ -316,6 +383,130 @@ fn auto_serialize_system<T: Component + Reflect + GetTypeRegistration>(
ev_update_description.send(UpdateEntityDescription);
}
fn notify_asset_change_system<T: Asset>(
mut ev_inspect: EventWriter<UpdateInspect<Handle<T>>>,
mut asset_changing: EventReader<AssetEvent<T>>,
mut selected: Query<&Handle<T>, With<Selected>>,
) {
for ev in asset_changing.read() {
for handle in selected.iter_mut() {
if ev.is_modified(handle.id()) {
ev_inspect.send(UpdateInspect::<Handle<T>>::default());
}
}
}
}
fn auto_serialize_asset<T: Asset + Reflect + GetTypeRegistration>(
q_t: Query<&Handle<T>, With<Selected>>,
mut description: ResMut<EntityDescription>,
registry: Res<AppTypeRegistry>,
mut ev_inspect: EventReader<UpdateInspect<Handle<T>>>,
mut ev_update_description: EventWriter<UpdateEntityDescription>,
assets: Res<Assets<T>>,
) {
if ev_inspect.is_empty() {
return;
}
ev_inspect.clear();
if let Some(handle) = q_t.iter().next() {
if let Some(asset) = assets.get(handle) {
let registry = registry.read();
info!(
"type: {:?}",
T::get_type_registration()
.type_info()
.type_path()
.to_string()
);
let json = serde_json::to_value(ReflectSerializer::new(asset, &registry)).unwrap();
info!("json: {:?}", json.to_string());
description.components.insert(
T::get_type_registration()
.type_info()
.type_path()
.to_string(),
json,
);
}
} else {
description
.components
.remove(T::get_type_registration().type_info().type_path());
}
ev_update_description.send(UpdateEntityDescription);
}
#[derive(Reflect, Default)]
#[reflect(Default)]
struct ShortMaterial {
pub base_color: Color,
}
impl ShortMaterial {
pub fn new(material: &StandardMaterial) -> Self {
Self {
base_color: material.base_color,
}
}
}
fn serialize_standart_materal(
q_m: Query<&Handle<StandardMaterial>>,
mut description: ResMut<EntityDescription>,
registry: Res<AppTypeRegistry>,
mut ev_inspect: EventReader<UpdateInspect<Handle<StandardMaterial>>>,
mut ev_update_description: EventWriter<UpdateEntityDescription>,
assets: Res<Assets<StandardMaterial>>,
) {
if ev_inspect.is_empty() {
return;
}
ev_inspect.clear();
if let Some(handle) = q_m.iter().next() {
if let Some(material) = assets.get(handle) {
let registry = registry.read();
let short_material = ShortMaterial::new(material);
let json =
serde_json::to_value(ReflectSerializer::new(&short_material, &registry)).unwrap();
let json = json[ShortMaterial::get_type_registration()
.type_info()
.type_path()
.to_string()]
.clone();
let json = json!(
{
StandardMaterial::get_type_registration()
.type_info()
.type_path()
.to_string(): json
});
info!("json: {:?}", json.to_string());
description.components.insert(
StandardMaterial::get_type_registration()
.type_info()
.type_path()
.to_string(),
json,
);
}
} else {
description.components.remove(
StandardMaterial::get_type_registration()
.type_info()
.type_path(),
);
}
ev_update_description.send(UpdateEntityDescription);
}
fn update_description(
description: ResMut<EntityDescription>,
mut ev_update_description: EventReader<UpdateEntityDescription>,