Skip to content

Commit

Permalink
Merge pull request #93 from unyt-org/feat-webrtc
Browse files Browse the repository at this point in the history
Implement WebRTC communication interface and MediaStreams
  • Loading branch information
benStre authored Mar 20, 2024
2 parents 7f1bbd9 + bbd01d6 commit a11f66d
Show file tree
Hide file tree
Showing 37 changed files with 1,111 additions and 392 deletions.
49 changes: 41 additions & 8 deletions compiler/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { BinaryCode } from "./binary_codes.ts";
import { Scope } from "../types/scope.ts";
import { ProtocolDataType } from "./protocol_types.ts";
import { Quantity } from "../types/quantity.ts";
import { EXTENDED_OBJECTS, INHERITED_PROPERTIES, VOID, SLOT_WRITE, SLOT_READ, SLOT_EXEC, NOT_EXISTING, SLOT_GET, SLOT_SET, DX_IGNORE, DX_BOUND_LOCAL_SLOT, DX_REPLACE } from "../runtime/constants.ts";
import { EXTENDED_OBJECTS, INHERITED_PROPERTIES, VOID, SLOT_WRITE, SLOT_READ, SLOT_EXEC, NOT_EXISTING, SLOT_GET, SLOT_SET, DX_IGNORE, DX_BOUND_LOCAL_SLOT, DX_REPLACE, DX_PTR } from "../runtime/constants.ts";
import { arrayBufferToBase64, base64ToArrayBuffer, buffer2hex, hex2buffer } from "../utils/utils.ts";
import { RuntimePerformance } from "../runtime/performance_measure.ts";
import { Conjunction, Disjunction, Logical, Negation } from "../types/logic.ts";
Expand All @@ -48,6 +48,8 @@ import { VolatileMap } from "../utils/volatile-map.ts";
// await wasm_init();
// wasm_init_runtime();

let WebRTCInterface: undefined|typeof import("../network/communication-interfaces/webrtc-interface.ts").WebRTCInterface;

export const activePlugins:string[] = [];

// for actions on variables, pointers, ...
Expand Down Expand Up @@ -2513,7 +2515,7 @@ export class Compiler {
Compiler.builder.insertVariable(SCOPE, assignment[0], ACTION_TYPE.GET, undefined, BinaryCode.INTERNAL_VAR); // parent
Compiler.builder.handleRequiredBufferSize(SCOPE.b_index, SCOPE);
SCOPE.uint8[SCOPE.b_index++] = BinaryCode.CHILD_SET;
Compiler.builder.insert(assignment[1], SCOPE, true, undefined, undefined, false); // insert key (don't save insert index for key value)
Compiler.builder.insert(assignment[1], SCOPE, true, undefined, undefined, false, false); // insert key (don't save insert index for key value)
Compiler.builder.insertVariable(SCOPE, assignment[2], ACTION_TYPE.GET, undefined, BinaryCode.INTERNAL_VAR); // value
}
Compiler.builder.handleRequiredBufferSize(SCOPE.b_index+1, SCOPE);
Expand Down Expand Up @@ -2832,6 +2834,15 @@ export class Compiler {
// exception for functions: convert to Datex.Function & create Pointer reference (proxifyValue required!)
if (value instanceof Function && !(value instanceof DatexFunction) && !(value instanceof JSTransferableFunction)) value = Pointer.proxifyValue(DatexFunction.createFromJSFunction(value));

// excpetion for MediaStream: always register as WebRTC mediastream when transmitting
if (globalThis.MediaStream && value instanceof MediaStream) {
if (!WebRTCInterface) throw new Error("Cannot bind MediaStream to WebRTCInterface (not yet initialized)")
WebRTCInterface.registerMediaStream(value);
}

// streams: always create pointer binding
if (value instanceof Stream) value = Pointer.proxifyValue(value);

// exception for Date: convert to Time (TODO: different approach?)
if (value instanceof Date && !(value instanceof Time)) {
try {
Expand Down Expand Up @@ -2902,6 +2913,10 @@ export class Compiler {
if (option_collapse && !SCOPE.options.no_create_pointers && !skip_first_collapse) SCOPE.uint8[SCOPE.b_index++] = BinaryCode.CREATE_POINTER;
}

// temporary to find errors: throw if a cloned html element without a unique ptr id
if (globalThis.HTMLElement && value instanceof HTMLElement && value.hasAttribute("uix-ptr") && !(value as any)[DX_PTR]) console.error("Invalid cloned HTMLElement " + value.tagName + (value.hasAttribute("id")?"#"+value.getAttribute("id"):""));


// first value was collapsed (if no_proxify == false, it was still collapsed because it's not a pointer reference)
// if (SCOPE.options.collapse_first_inserted) SCOPE.options.collapse_first_inserted = false; // reset
SCOPE.options._first_insert_done = true;
Expand Down Expand Up @@ -3172,7 +3187,7 @@ export class Compiler {

// special exception: insert raw datex script (dxb Scope can be inserted normally (synchronous))
if (d instanceof DatexResponse && !(d.datex instanceof Scope)) await Compiler.builder.compilerInsert(SCOPE, d);
else Compiler.builder.insert(d, SCOPE);
else Compiler.builder.insert(d, SCOPE, undefined, undefined, undefined, undefined, false); // disable replace optimization for now to prevent invalid replacement, e.g. for path properties
}
isEffectiveValue = true;
}
Expand Down Expand Up @@ -5156,10 +5171,28 @@ export class Compiler {
) : SCOPE.buffer;
}

/**
* Workaround: recursively cache all blob values in the iterable
*/
static cacheValues(iterable: Iterable<unknown>, promises:Promise<void>[] = []): Promise<void>[] {
for (const val of iterable) {
if (val instanceof Blob) promises.push(Runtime.cacheValue(val));
else if (typeof val == "object" && (val as any)?.[Symbol.iterator]) this.cacheValues((val as any), promises);
}
return promises;
}


// compile loop
static async compileLoop(SCOPE:compiler_scope):Promise<ArrayBuffer|ReadableStream<ArrayBuffer>> {

// make sure WebRTC interface is loaded
({ WebRTCInterface } = await import("../network/communication-interfaces/webrtc-interface.ts"));

// cache inserted blob values
const promises = SCOPE.data ? this.cacheValues(SCOPE.data) : [];
if (promises.length) await Promise.all(promises);

const body_compile_measure = RuntimePerformance.enabled ? RuntimePerformance.startMeasure("compile time", "body") : undefined;

if (!SCOPE.datex) SCOPE.datex = ";";//throw new CompilerError("DATEX Script is empty");
Expand Down Expand Up @@ -5541,22 +5574,22 @@ export class Compiler {

/** create a dxb file created from a DATEX Script string and convert to data url */
static async datexScriptToDataURL(dx:string, type = ProtocolDataType.DATA):Promise<string> {
let dxb = <ArrayBuffer> await Compiler.compile(dx, [], {sign:false, encrypt: false, type})
const dxb = <ArrayBuffer> await Compiler.compile(dx, [], {sign:false, encrypt: false, type})

let blob = new Blob([dxb], {type: "text/dxb"}); // workaround to prevent download
const blob = new Blob([dxb], {type: "text/dxb"}); // workaround to prevent download

return new Promise(resolve=>{
var a = new FileReader();
const a = new FileReader();
a.onload = function(e) {resolve(<string>e.target.result);}
a.readAsDataURL(blob);
});
}

/** create a dxb file created from a DATEX Script string and convert to object url */
static async datexScriptToObjectURL(dx:string, type = ProtocolDataType.DATA):Promise<string> {
let dxb = <ArrayBuffer> await Compiler.compile(dx, [], {sign:false, encrypt: false, type})
const dxb = <ArrayBuffer> await Compiler.compile(dx, [], {sign:false, encrypt: false, type})

let blob = new Blob([dxb], {type: "text/dxb"}); // workaround to prevent download
const blob = new Blob([dxb], {type: "text/dxb"}); // workaround to prevent download
return URL.createObjectURL(blob);
}

Expand Down
19 changes: 18 additions & 1 deletion datex_short.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ declare global {
*/
const isolate: typeof Ref.disableCapturing

/**
* The local endpoint of the current runtime (alias for Datex.Runtime.endpoint)
*/
const localEndpoint: Endpoint

// conflict with UIX.template (confusing)
// const template: typeof _template;
}
Expand All @@ -78,6 +83,7 @@ globalThis.timeout = _timeout;
// @ts-ignore global
globalThis.sync = _sync;


// can be used instead of import(), calls a DATEX get instruction, works for urls, endpoint, ...
export async function get<T=unknown>(dx:string|URL|Endpoint, assert_type?:Type<T> | Class<T> | string, context_location?:URL|string, plugins?:string[]):Promise<T> {
// auto retrieve location from stack
Expand Down Expand Up @@ -170,7 +176,16 @@ Object.defineProperty(_datex, 'get', {value:(res:string, type?:Class|Type, locat
// add globalThis.meta
// Object.defineProperty(globalThis, 'meta', {get:()=>getMeta(), set:()=>{}, configurable:false})

export const datex = <typeof _datex & {meta:datex_meta, get:typeof get}><unknown>_datex;
export const datex = <typeof _datex & {
/**
* metadata associated with the current function call
*/
meta:datex_meta,
/**
* get a resource via datex
*/
get:typeof get
}><unknown>_datex;
// @ts-ignore global datex
globalThis.datex = datex;
// global access to datex and meta
Expand Down Expand Up @@ -668,6 +683,8 @@ Object.defineProperty(globalThis, 'grantAccess', {value:grantAccess, configurabl
Object.defineProperty(globalThis, 'grantPublicAccess', {value:grantPublicAccess, configurable:false})
Object.defineProperty(globalThis, 'revokeAccess', {value:revokeAccess, configurable:false})

Object.defineProperty(globalThis, 'localEndpoint', {get: ()=>Runtime.endpoint, configurable:false})

// @ts-ignore
globalThis.get = get
// @ts-ignore
Expand Down
2 changes: 1 addition & 1 deletion docs/api/runtime/js_interface.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
create a custom DATEX JS Interface for a type with handlers

- serialize efficiently with the serialize function and de-serialize in the cast function
- do not use @sync classes in combination with an additional js_interface_configuration!;
- do not use struct classes in combination with an additional js_interface_configuration!;

## class **JSInterface**
### Properties
Expand Down
18 changes: 10 additions & 8 deletions docs/manual/01 Introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,24 +51,26 @@ how pointers are synchronized between endpoints.

### Creating DATEX-compatible classes

With the `@sync` decorator, a class can be bound to a new DATEX type.
With the `struct` wrapper, a class can be bound to a new DATEX type.

All instance properties decorated with `@property` are bound to the DATEX value and also visible when the value is shared between endpoints.
Per default, the properties are local and only available in the current JavaScript context.

```ts
@sync class MyObject {
@property a = 10
@property b = 20
localProp = 4
}
const MyObject = struct(
class {
@property a = 10
@property b = 20
localProp = 4
}
)

const obj = new MyObject();
```

Instances of a class marked with `@sync` are also automatically bound to a pointer when created (The value does not have to be explicitly wrapped in `$$()`).
Instances of a class wrapped with `struct` are also automatically bound to a pointer when created (The value does not have to be explicitly wrapped in `$$()`).

Read more about `@sync` classes [here](./11%20Classes.md).
Read more about `struct` classes [here](./12%20Classes.md).

### Persistent data

Expand Down
4 changes: 2 additions & 2 deletions docs/manual/03 Pointers.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,9 @@ Datex.Ref.isRef(map.get('y')) // true, was implicitly bound to a pointer
There are some exceptions to this behaviour:
1. Primitive property values are not converted to pointers per default
2. Normal [class instances](./10%20Types.md#jsobject) (`js:Object`) are not converted to pointers per default.
Instances of [`@sync`](11%20Classes.md) classes are still converted to pointers
Instances of [`struct`](12%20Classes.md) classes are still converted to pointers
3. When a [class instances](./10%20Types.md#jsobject) is directly bound to a pointer with `$$()`, its
properties are not converted to pointers per default (like 2., this does not affect `@sync` class instances
properties are not converted to pointers per default (like 2., this does not affect `struct` class instances



Expand Down
2 changes: 1 addition & 1 deletion docs/manual/05 Eternal Pointers.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ This guarantees that `eternal` can be used synchronously (without `await`).
For the following usecases, the asynchronous `lazyEternal`/`lazyEternalVar` label should be used instead of `eternal`/`eternalVar`:

* A value that consumes lots of memory and is only actually needed when certain conditions are met
* A value that requires custom JavaScript bindings (e.g. a `@sync` class instance). JavaScript bindings cannnot be properly initialized at endpoint startup if the corresponding JavaScript class definition is not yet loaded.
* A value that requires custom JavaScript bindings (e.g. a `struct` class instance). JavaScript bindings cannnot be properly initialized at endpoint startup if the corresponding JavaScript class definition is not yet loaded.

The `lazyEternal`/`lazyEternalVar` label can be used the same was as the `eternal` label, only requiring an additional `await`:

Expand Down
Loading

0 comments on commit a11f66d

Please sign in to comment.