Skip to content

Commit

Permalink
update datex-core wasm, support filter and other array methods in alw…
Browse files Browse the repository at this point in the history
…ays()
  • Loading branch information
benStre committed Dec 3, 2023
1 parent 13a69b0 commit 9ff51cc
Show file tree
Hide file tree
Showing 15 changed files with 273 additions and 21 deletions.
135 changes: 135 additions & 0 deletions deno.lock

Large diffs are not rendered by default.

35 changes: 35 additions & 0 deletions functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,41 @@ export function map<T, U, O extends 'array'|'map' = 'array'>(iterable: Iterable<
}


// TODO: (remove empty entries inbetween)
// export function filter<T, U>(array: Array<T>, predicate: (value: T, index: number, array: T[]) => value is T&U) {

// // live map
// if (Datex.Ref.isRef(array)) {

// const filtered:U[] = $$([])

// const spliceArray = true;

// new IterableHandler<T,U>(array, {
// filter: (v,k):v is T&U => {
// return predicate(v,k,array)
// },
// onEntryRemoved: (v,k) => {
// if (spliceArray) filtered.splice(k, 1);
// else delete filtered[k];
// },
// onNewEntry: (v,k) => {
// filtered[k] = v
// },
// onEmpty: () => {
// filtered.length = 0
// }
// })

// return filtered;

// }

// // static map
// else return array.filter(predicate)
// }


/**
* Switches between two values depending if a value is true or false
* @param value input value
Expand Down
2 changes: 2 additions & 0 deletions runtime/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export const DX_PERMISSIONS_X: unique symbol = Symbol("DX_PERMISSIONS_X");
export const DX_IGNORE: unique symbol = Symbol("DX_IGNORE"); // ignore in DX (when serializing, only works for elements in array-like values)
export const DX_PREEMPTIVE: unique symbol = Symbol("DX_PREEMPTIVE"); // used to override the default loading behaviour for a pointer (fetching by id). Can be an arbitrary DATEX Script that can be resolved with datex.get. Currently only used by the interface generator for JS modules.

export const DX_REACTIVE_METHODS: unique symbol = Symbol("DX_REACTIVE_METHODS"); // object containing reactive methods for this obj, used e.g. for x.$.map, x.$.filter, ...

export const INIT_PROPS: unique symbol = Symbol("INIT_PROPS"); // key for init props function passed to constructor to initialize properties of pointer immediately

// -------------------------------------
Expand Down
1 change: 1 addition & 0 deletions runtime/js_interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export type js_interface_configuration<T=any> = {

is_normal_object?: boolean, // if true, handle properties like object properties (no custom handling), ignore add_property, set_property, etc.

get_reactive_methods_object?: (ptr: Pointer) => Record<string, any>, // returns an object that is mapped to $ methods on the ptr value

type_params_match?: (params:any[], against_params:any[])=>boolean, // implement for parmetrized types -> return if parameters match

Expand Down
82 changes: 61 additions & 21 deletions runtime/pointers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { Endpoint, endpoints, endpoint_name, IdEndpoint, Person, Target, target_clause, LOCAL_ENDPOINT } from "../types/addressing.ts";
import { NetworkError, PermissionError, PointerError, RuntimeError, ValueError } from "../types/errors.ts";
import { Compiler, PrecompiledDXB } from "../compiler/compiler.ts";
import { DX_PTR, DX_VALUE, INVALID, NOT_EXISTING, SET_PROXY, SHADOW_OBJECT, UNKNOWN_TYPE, VOID } from "./constants.ts";
import { DX_PTR, DX_REACTIVE_METHODS, DX_VALUE, INVALID, NOT_EXISTING, SET_PROXY, SHADOW_OBJECT, UNKNOWN_TYPE, VOID } from "./constants.ts";
import { Runtime, UnresolvedValue } from "./runtime.ts";
import { DEFAULT_HIDDEN_OBJECT_PROPERTIES, logger, TypedArray } from "../utils/global_values.ts";
import type { compile_info, datex_scope, Equals, PointerSource } from "../utils/global_types.ts";
Expand All @@ -22,12 +22,12 @@ import { Time } from "../types/time.ts";
import "../types/native_types.ts"; // getAutoDefault
import { displayFatalError } from "./display.ts";
import { JSTransferableFunction } from "../types/js-function.ts";
import { map } from "../functions.ts";
import { sha256 } from "../utils/sha256.ts";
import { AutoMap } from "../utils/auto_map.ts";
import { IterableWeakSet } from "../utils/iterable-weak-set.ts";
import { IterableWeakMap } from "../utils/iterable-weak-map.ts";
import { LazyPointer } from "./lazy-pointer.ts";
import { ReactiveArrayMethods } from "../types/reactive-methods/array.ts";

export type observe_handler<K=any, V extends RefLike = any> = (value:V extends RefLike<infer T> ? T : V, key?:K, type?:Ref.UPDATE_TYPE, transform?:boolean, is_child_update?:boolean)=>void|boolean
export type observe_options = {types?:Ref.UPDATE_TYPE[], ignore_transforms?:boolean, recursive?:boolean}
Expand Down Expand Up @@ -130,11 +130,15 @@ export abstract class Ref<T = any> extends EventTarget {
return Reflect.getOwnPropertyDescriptor(pointer.val, p);
}

const arrayFunctions:Record<string,Function> = {};
if (pointer.val instanceof Array) {
arrayFunctions.map = function(handler:(value: unknown, index: number, array: Iterable<unknown>) => unknown) {
return map(pointer.val, handler)
}
const type = Type.ofValue(pointer.val);

// add DX_REACTIVE_METHODS for array
if (pointer.val instanceof Array && !(pointer.val as any)[DX_REACTIVE_METHODS]) {
(pointer.val as any)[DX_REACTIVE_METHODS] = new ReactiveArrayMethods(pointer);
}
// add custom DX_REACTIVE_METHODS
else if (type.interface_config?.get_reactive_methods_object) {
(pointer.val as any)[DX_REACTIVE_METHODS] = type.interface_config.get_reactive_methods_object(pointer.val);
}

handler.get = (_, key) => {
Expand All @@ -154,16 +158,10 @@ export abstract class Ref<T = any> extends EventTarget {

if (typeof key == "symbol") return pointer.val?.[key];

// special array $ methods (map, ...)
if (pointer.val instanceof Array) {
if (typeof key == "string" && key in arrayFunctions) return arrayFunctions[key];
}
// special $ methods
const reactiveMethods = pointer.val?.[DX_REACTIVE_METHODS];
if (reactiveMethods && key in reactiveMethods) return reactiveMethods[key];

// special map $ methods (get, ...)
if (pointer.val instanceof Map) {
if (key === "get") return (k: any) => PointerProperty.get(pointer, <keyof typeof pointer>k);
}

if (force_pointer_properties) return PointerProperty.get(pointer, <keyof typeof pointer>key, true);
else {
if (!(pointer.val instanceof Array) && ![...pointer.getKeys()].includes(key)) {
Expand Down Expand Up @@ -401,7 +399,6 @@ export abstract class Ref<T = any> extends EventTarget {
return res;
}


/**
* must be called each time before the current collapsed value of the Ref is requested
* to keep track of dependencies and update transform
Expand All @@ -412,6 +409,7 @@ export abstract class Ref<T = any> extends EventTarget {
*/
handleBeforeNonReferencableGet(key:any = NOT_EXISTING) {
if (Ref.freezeCapturing) return;

// remember previous capture state
const previousGetters = Ref.capturedGetters;
const previousGettersWithKeys = Ref.capturedGettersWithKeys;
Expand Down Expand Up @@ -971,6 +969,33 @@ export type SmartTransformOptions = {
cache?: boolean
}

const observableArrayMethods = new Set<string>([
"entries",
"filter",
"find",
"findIndex",
"findLast",
"findLastIndex",
"flat",
"flatMap",
"forEach",
"includes",
"indexOf",
"join",
"keys",
"lastIndexOf",
"map",
"reduce",
"reduceRight",
"slice",
"some",
"toReversed",
"toSorted",
"toSpliced",
"values",
"with"
])

/** Wrapper class for all pointer values ($xxxxxxxx) */
export class Pointer<T = any> extends Ref<T> {

Expand Down Expand Up @@ -3076,9 +3101,8 @@ export class Pointer<T = any> extends Ref<T> {
this.handleSet(name, val);
},
get: () => {
// this.handleBeforePrimitiveValueGet();
// important: reference shadow_object, not this.shadow_object here, otherwise it might get garbage collected
this.handleBeforeNonReferencableGet(name);
// important: reference shadow_object, not this.shadow_object here, otherwise it might get garbage collected
return Ref.collapseValue(shadow_object[name], true, true);
}
});
Expand Down Expand Up @@ -3114,8 +3138,10 @@ export class Pointer<T = any> extends Ref<T> {
if (this.#custom_prop_getter && (!this.shadow_object || !(key in this.shadow_object)) && !(typeof key == "symbol")) return this.#custom_prop_getter(key);
const val:any = Ref.collapseValue(this.shadow_object?.[key], true, true);

if (key != "$" && key != "$$")
this.handleBeforeNonReferencableGet(key);
if (key != "$" && key != "$$") {
if (is_array) this.handleBeforeNonReferenceableGetArray(key);
else this.handleBeforeNonReferencableGetObject(key)
}

// should fix #private properties, but does not seem to work for inheriting classes?
if (typeof val == "function"
Expand Down Expand Up @@ -3222,6 +3248,20 @@ export class Pointer<T = any> extends Ref<T> {
}
}


private handleBeforeNonReferenceableGetArray(key: string|symbol) {
// assumes map, filter, etc. gets called after property is accessed
if (typeof key=="string" && observableArrayMethods.has(key)) this.handleBeforeNonReferencableGet()
else this.handleBeforeNonReferencableGetObject(key);
}

private handleBeforeNonReferencableGetObject(key: string|symbol) {
if (Object.hasOwn(this.shadow_object!, key)) {
this.handleBeforeNonReferencableGet(key);
}
}


// get property by key
// if leak_js_properties is true, primitive + prototype methods (like toString are also accessible) - should only be used in JS context
public getProperty(key:unknown, leak_js_properties = false) {
Expand Down
3 changes: 3 additions & 0 deletions types/native_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { INVALID, NOT_EXISTING } from "../runtime/constants.ts";
import { Tuple } from "./tuple.ts";

import "../utils/auto_map.ts"
import { ReactiveMapMethods } from "./reactive-methods/map.ts";

// @ts-ignore accecssible to dev console
globalThis.serializeImg = (img:HTMLImageElement)=> {
Expand Down Expand Up @@ -134,6 +135,8 @@ Type.std.Map.setJSInterface({
return value;
},

get_reactive_methods_object: (ptr) => new ReactiveMapMethods(ptr),

set_property_silently: (parent:Map<any,any>, key, value, pointer) => Map.prototype.set.call(parent, key, value),
delete_property_silently: (parent:Map<any,any>, key, pointer) => Map.prototype.delete.call(parent, key),
clear_silently: (parent:Map<any,any>, pointer) => Map.prototype.clear.call(parent),
Expand Down
16 changes: 16 additions & 0 deletions types/reactive-methods/array.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Pointer } from "../../runtime/pointers.ts";
import { filter, map } from "../../functions.ts";

export class ReactiveArrayMethods<T=unknown> {

constructor(private pointer: Pointer) {}

map<U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): U[] {
return map(this.pointer.val, thisArg ? callbackfn.bind(thisArg) : callbackfn as any)
}

// TODO
// filter<S extends T>(predicate: (value: T, index: number, array: T[]) => value is S, thisArg?: any): S[] {
// return filter(this.pointer.val, thisArg ? predicate.bind(thisArg) : predicate as any)
// }
}
10 changes: 10 additions & 0 deletions types/reactive-methods/map.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Pointer, PointerProperty } from "../../runtime/pointers.ts";

export class ReactiveMapMethods<K=unknown, V=unknown> {

constructor(private pointer: Pointer) {}

get(k: any) {
return PointerProperty.get(this.pointer, <keyof typeof this.pointer>k);
}
}
10 changes: 10 additions & 0 deletions utils/iterable-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,23 @@ import { ValueError } from "../datex_all.ts";
export class IterableHandler<T, U = T> {

private map: ((value: T, index: number, array: Iterable<T>) => U) | undefined
private filter: ((value: T, index: number, array: Iterable<T>) => value is T&U) | undefined

private onNewEntry: (entry:U, key:number,) => void
private onEntryRemoved: (entry:U, key:number,) => void
private onEmpty?: () => void


constructor(private iterable: Datex.RefOrValue<Iterable<T>>, callbacks: {
map?: (value: T, index: number, array: Iterable<T>) => U,
// TODO:
// filter?: (value: T, index: number, array: Iterable<T>) => value is T&U,
onNewEntry: (this: IterableHandler<T, U>, entry:U, key:number) => void
onEntryRemoved: (entry: U, key:number) => void,
onEmpty?: () => void
}) {
this.map = callbacks.map;
this.filter = callbacks.filter;
this.onNewEntry = callbacks.onNewEntry;
this.onEntryRemoved = callbacks.onEntryRemoved;
this.onEmpty = callbacks.onEmpty;
Expand Down Expand Up @@ -136,6 +141,11 @@ export class IterableHandler<T, U = T> {
key = Number(key);
const entry = this.valueToEntry(value, key)

// TODO: remove entries inbetween
// if (this.filter && !this.filter(value, key, val(this.iterable))) {
// return;
// }

if (key != undefined) {
// TODO: is this correct
if (!this.isPseudoIndex() && this.entries.has(key)) this.handleRemoveEntry(key) // entry is overridden
Expand Down
Binary file modified wasm/adapter/pkg/datex_wasm_bg.wasm
Binary file not shown.
Binary file modified wasm/adapter/target/wasm32-unknown-unknown/release/datex_wasm.wasm
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

0 comments on commit 9ff51cc

Please sign in to comment.