A react renderer for Resonite, copying template "elements".
There are two parts of the React-Resonite architecture:
- React-Resonite Renderer Webserver (Server)
- Resonite React-Resonite renderer object (Client)
They communicate via a websocket.
The server is a nodejs websocket server running a custom react renderer.
The React-Reso library uses typescript, and should have full property support. If you encounter issues with the intellisense, make a bug.
Each websocket connection gets a unique react instance, it will continue to send update messages to the client as the react dom bound to the connection updates.
A hello world that makes a red box:
import React from "react";
import { rr, createRender, wsProxyServer, componentDefs, useResoRef } from "react-reso";
export function runServer() {
// componentDefs defines what templates are available, this should be renamed.
const render = createRender(<RedBox />, componentDefs);
wsProxyServer(render, { port: 8080 });
console.log("Server started on port 8080");
}
function RedBox() {
// This is how references between elements are done.
// (If you know react already, internally this is a 'useState({})' and will cause a re-render when the reference is populated, unlike normal 'useRef')
const [{ material }, matRefGetter] = useResoRef(rr.unlitMaterial);
const [{ mesh }, meshRefGetter] = useResoRef(rr.boxMesh);
// React you use lowercase strings for dom elements, in React-Resonite the names are elements.
// rr provides intellisense for React-Reso 'dom' elements.
return <rr.transform>
<rr.unlitMaterial color={{ r: 0.25 }} ref={matRefGetter} />
<rr.boxMesh size={{ x: 0.1, y: 0.1, z: 0.3 }} ref={meshRefGetter} />
<rr.meshRenderer position={{ z: 0.4 }} mesh={mesh} material={material} />
</rr.transform>;
}
runServer();
Messages from the server to client instruct the client how to instance and customize elements.
Messages from the client to server are not yet supported, but would allow events from resonite to invoke JS code.
The client two important slots, staging
and root
staging
is where elements are spawned, it is inactive by default and is considered a 'scratch pad'.root
is active, and where elements are visible. Elements are moved intoroot
via thesetParent
message.
The server will generally send events in this order:
- Send commands to spawn elements, customize them, and set their hierarchy inside of
staging
(forming a tree with 'root' elements) - Clear
root
- Reparent the 'root' elements in
staging
intoroot
viasetParent
The format uses only url-unsafe symbols as delimiters, Resonite only supports the URL encode and decode operations to encode data and that is used to pass strings that may otherwise include delimiter symbols.
Each message sent from the server to the client follows this format:
ws_msg: (msg '|')*;
msg: 'create' '+' element_id '+' template
| 'remove' '+' (element_id|ROOT)
| 'setParent' '+' element_id '+' (element_id|ROOT) '+'' (element_id|NULL)
| 'update' '+' element_id '+' (update '+')*
;
update: prop '=' type '=' (prop_value|NULL);
element_id: ID;
template: LABEL;
prop: LABEL:
type: LABEL;
prop_value: LABEL;
ID: [0-9]+;
ROOT: 'root';
NULL: '$'';
LABEL: [a-zA-Z0-9]+;
Some example messages:
- Create an instance of the
transform
element as1
(in staging) - Create an instance of the
box
element as2
(in staging) - Set the
hue
of the2
tored
- Set the parent of
2
to1
- Clear
root
- Move
1
underroot
- Create an instance of the
box
element as3
- Set the parent of
3
to1
, but have it be after2
- reset the
hue
of2
todefault
andactive
tofalse
MESSAGE ARE NOT NEWLINE DELIMITED
they are shown in rows to make them more legible.
create+1+transform|
create+2+box|
update+2+hue=color=[1;0;0;1]+|
setParent+2+1|
remove+root|
setParent+1+root|
create+3+box|
setParent+3+1+2|
update+2+hue=color=$+active=bool=false+|
Messages may be sent as concatenated strings, or as multiple messages. Either way they are processed logically in-order.
Due to it taking a few updates for dynamic variables to 'hook up'; create commands are run instantly (which kicks off the hook-up) and all other commands are delayed 10 update cycles. In practice this is not noticeable but if you see the delay in the client, that is why it is there.
Note: Removing root
does not remove the root
slot, it removes all children under root
.
Templates are the cold-stored form of an element, once the template is duplicated into staging
, then it becomes an element.
Templates (and their spawned elements) expose props that can be set, referenced, or both.
There are three kinds of props:
- Set - A field that can be assigned, but can not be directly referenced.
- Examples: If they're not to be referenced;
Active
,Name
... - Sets can often be RefSets, but you may not want to expose a prop as bindable.
- This may be useful for template inputs that are fixed, referenceable fields are normally changeable over time.
- Examples: If they're not to be referenced;
- Ref - A field that can be referenced, normally this is a static piece of information.
- Examples:
Textures
,Slots
,Impulse Inputs
,Field Outputs
,<Any field>
- Examples:
- RefSet
- Examples:
Active
,Name
,Position
- Examples:
For these templates, TRef
can mean SyncRef
if the inner type is a WorldElement
, or IField
if anything else.
# Template
Slot: /Template Name/
children:
- # Element-Proxy
Name: /Template dynamic variable ID, defaults to '*NO_ELEMENT_ID*'/
Tag: element-proxy
Components:
- # Element-Id-Broadcast
Type: ParentValue<String>
Tag: element-id
Value: <From drive>
- # Broadcast-Copier
Type: ValueCopy<String>
Source: Element-Proxy.Name
Target: Element-Id-Broadcast.Value
- # __this broadcast
Type: Parent<Slot>
Tag: __this
Value: Template # Possibly have this fall back to the parent of the proxy if not set.
- # __proxy broadcast
Type: Parent<Slot>
Tag: __proxy
Value: Element-Proxy
- # __children broadcast
# Optional, only if the template supports children.
Type: Parent<Slot>
Tag: __children
Value: /The slot to put children, this is often the root of the template/
- # Set / SetRef Fields - Data going into the template
# Includes Position, Rotation, Active, Outbound Impulses, Inbound Fields
Type: Parent<TRef</prop type/>>
Tag: /Property name/
Value: /The field being assigned/
- # Ref / SetRef Fields - References point into the template
# Child Slots, Inbound Impulses, Output Fields
Type: Parent</prop type/>
Tag: /Property name/
Value: /The field being assigned/
- # In a spawned out Element, DynamicValues will also be here.
# Those are not in the template, they are created at runtime.
Children: # Props
Prop are defined by these templates:
# Common Template
# All props have the same core logic
Slot: /Prop Name/
Components:
- # Element-Id-Receiver
Type: ParentLink<String>
MatchTag: element-id
Target: Dynamic-Variable-Name-Builder.Strings.0
DefaultValue: '*NO_PARENT_PROXY*'
- # Prop-Name-Copier-Name-Builder
Type: ValueCopy<String>
Source: Element-Proxy.Name
Target: Dynamic-Variable-Name-Builder.Strings.1
- # Prop-Name-Copier-Parent-Reference
Type: ValueCopy<String>
Source: Element-Proxy.Name
Target: /ParentReference.MatchTag field, see specializations/
- # Dynamic-Variable-Name-Builder
Type: StringConcatenationDriver
TargetString: /Dynamic(whatever).VariableName field, see specializations/
Separator: .
Strings:
- <From Element-Id-Receiver>
- <From Prop-Name-Copier-Name-Builder>
# The output format: `{proxy slot name of the element}.{prop name}`
# In an active element, proxy slot name is often `react_w/{element-id}`
# The final format: `react_w/{element-id}.{prop name}`
# Set Props
Slot: /Prop Name/
Components:
# All props from Common Template
- # Parent-Field-Receiver
Type: ParentLink<TRef</prop type/>>
MatchTag: /Set by Prop-Name-Copier-Parent-Reference.Target/
Target: Dynamic-Receiver.Target
- # Dynamic-Receiver
Type: DynamicVariableDriver</prop type/>
VariableName: /Set by Dynamic-Variable-Name-Builder.TargetString/
Target: /Set by Parent-Field-Receiver.Target/
DefaultValue: /Customize based on the default prop value/
# Ref Props
Slot: /Prop Name/
Components:
# All props from Common Template
- # Parent-Field-Receiver
Type: ParentLink</prop type/>
MatchTag: /Set by Prop-Name-Copier-Parent-Reference.Target/
Target: Dynamic-Receiver.Target
- # Dynamic-Receiver
Type: DynamicVariable</prop type/>
VariableName: /Set by Dynamic-Variable-Name-Builder.TargetString/
Target: /Set by Parent-Field-Receiver.Target/
# SetRef Props
Slot: /Prop Name/
Components:
# All props from Common Template
# TODO