Skip to content

Commit

Permalink
feat: add event methods, like rxjs
Browse files Browse the repository at this point in the history
  • Loading branch information
1ncounter committed Jul 31, 2024
1 parent a02b19e commit d4bdf14
Show file tree
Hide file tree
Showing 16 changed files with 429 additions and 180 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ export function createRenderer<RenderObject = IRenderObject>(
},
destroy: () => {
lifeCycleService.setPhase(LifecyclePhase.Destroying);
instantiationService.dispose();
},
};

Expand Down
3 changes: 1 addition & 2 deletions packages/renderer-core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* --------------- api -------------------- */
export { createRenderer } from './main';
export { createRenderer } from './createRenderer';
export { IExtensionHostService } from './services/extension';
export { definePackageLoader, IPackageManagementService } from './services/package';
export { LifecyclePhase, ILifeCycleService } from './services/lifeCycleService';
Expand All @@ -9,7 +9,6 @@ export { IRuntimeIntlService } from './services/runtimeIntlService';
export { IRuntimeUtilService } from './services/runtimeUtilService';
export { ISchemaService } from './services/schema';
export { Widget } from './widget';
export * from './utils/value';

/* --------------- types ---------------- */
export type * from './types';
Expand Down
76 changes: 45 additions & 31 deletions packages/renderer-core/src/services/code-runtime/codeRuntime.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,34 @@
import {
type StringDictionary,
type JSNode,
type EventDisposable,
type IDisposable,
type JSExpression,
type JSFunction,
specTypes,
isNode,
toDisposable,
Disposable,
} from '@alilc/lowcode-shared';
import { type ICodeScope, CodeScope } from './codeScope';
import { isNode } from '../../../../shared/src/utils/node';
import { mapValue } from '../../utils/value';
import { mapValue } from './value';
import { evaluate } from './evaluate';

export interface CodeRuntimeOptions<T extends StringDictionary = StringDictionary> {
initScopeValue?: Partial<T>;

parentScope?: ICodeScope;

evalCodeFunction?: EvalCodeFunction;
}

export interface ICodeRuntime<T extends StringDictionary = StringDictionary> {
export interface ICodeRuntime<T extends StringDictionary = StringDictionary> extends IDisposable {
getScope(): ICodeScope<T>;

run<R = unknown>(code: string, scope?: ICodeScope): R | undefined;

resolve(value: StringDictionary): any;

onResolve(handler: NodeResolverHandler): EventDisposable;
onResolve(handler: NodeResolverHandler): IDisposable;

createChild<V extends StringDictionary = StringDictionary>(
options: Omit<CodeRuntimeOptions<V>, 'parentScope'>,
Expand All @@ -34,49 +37,55 @@ export interface ICodeRuntime<T extends StringDictionary = StringDictionary> {

export type NodeResolverHandler = (node: JSNode) => JSNode | false | undefined;

let onResolveHandlers: NodeResolverHandler[] = [];

export type EvalCodeFunction = (code: string, scope: any) => any;

export class CodeRuntime<T extends StringDictionary = StringDictionary> implements ICodeRuntime<T> {
private codeScope: ICodeScope<T>;
export class CodeRuntime<T extends StringDictionary = StringDictionary>
extends Disposable
implements ICodeRuntime<T>
{
private _codeScope: ICodeScope<T>;

private _evalCodeFunction: EvalCodeFunction = evaluate;

private evalCodeFunction: EvalCodeFunction = evaluate;
private _resolveHandlers: NodeResolverHandler[] = [];

constructor(options: CodeRuntimeOptions<T> = {}) {
if (options.evalCodeFunction) this.evalCodeFunction = options.evalCodeFunction;
super();

if (options.parentScope) {
this.codeScope = options.parentScope.createChild<T>(options.initScopeValue ?? {});
} else {
this.codeScope = new CodeScope(options.initScopeValue ?? {});
}
if (options.evalCodeFunction) this._evalCodeFunction = options.evalCodeFunction;
this._codeScope = this.addDispose(
options.parentScope
? options.parentScope.createChild<T>(options.initScopeValue ?? {})
: new CodeScope(options.initScopeValue ?? {}),
);
}

getScope() {
return this.codeScope;
return this._codeScope;
}

run<R = unknown>(code: string): R | undefined {
this._throwIfDisposed(`this code runtime has been disposed`);

if (!code) return undefined;

try {
const result = this.evalCodeFunction(code, this.codeScope.value);
const result = this._evalCodeFunction(code, this._codeScope.value);

return result as R;
} catch (err) {
// todo replace logger
console.error('eval error', code, this.codeScope.value, err);
console.error('eval error', code, this._codeScope.value, err);
return undefined;
}
}

resolve(data: StringDictionary): any {
if (onResolveHandlers.length > 0) {
if (this._resolveHandlers.length > 0) {
data = mapValue(data, isNode, (node: JSNode) => {
let newNode: JSNode | false | undefined = node;

for (const handler of onResolveHandlers) {
for (const handler of this._resolveHandlers) {
newNode = handler(newNode as JSNode);
if (newNode === false || typeof newNode === 'undefined') {
break;
Expand Down Expand Up @@ -110,20 +119,25 @@ export class CodeRuntime<T extends StringDictionary = StringDictionary> implemen
/**
* 顺序执行 handler
*/
onResolve(handler: NodeResolverHandler): EventDisposable {
onResolveHandlers.push(handler);
return () => {
onResolveHandlers = onResolveHandlers.filter((h) => h !== handler);
};
onResolve(handler: NodeResolverHandler): IDisposable {
this._resolveHandlers.push(handler);

return this.addDispose(
toDisposable(() => {
this._resolveHandlers = this._resolveHandlers.filter((h) => h !== handler);
}),
);
}

createChild<V extends StringDictionary = StringDictionary>(
options?: Omit<CodeRuntimeOptions<V>, 'parentScope'>,
): ICodeRuntime<V> {
return new CodeRuntime({
initScopeValue: options?.initScopeValue,
parentScope: this.codeScope,
evalCodeFunction: options?.evalCodeFunction ?? this.evalCodeFunction,
});
return this.addDispose(
new CodeRuntime({
initScopeValue: options?.initScopeValue,
parentScope: this._codeScope,
evalCodeFunction: options?.evalCodeFunction ?? this._evalCodeFunction,
}),
);
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import {
createDecorator,
invariant,
Disposable,
type StringDictionary,
type IDisposable,
} from '@alilc/lowcode-shared';
import { type ICodeRuntime, type CodeRuntimeOptions, CodeRuntime } from './codeRuntime';
import { ISchemaService } from '../schema';

export interface ICodeRuntimeService {
export interface ICodeRuntimeService extends IDisposable {
readonly rootRuntime: ICodeRuntime;

createCodeRuntime<T extends StringDictionary = StringDictionary>(
Expand All @@ -18,15 +18,18 @@ export interface ICodeRuntimeService {
export const ICodeRuntimeService = createDecorator<ICodeRuntimeService>('codeRuntimeService');

export class CodeRuntimeService extends Disposable implements ICodeRuntimeService {
rootRuntime: ICodeRuntime;
private _rootRuntime: ICodeRuntime;
get rootRuntime() {
return this._rootRuntime;
}

constructor(
options: CodeRuntimeOptions = {},
@ISchemaService private schemaService: ISchemaService,
) {
super();
this.rootRuntime = new CodeRuntime(options);

this._rootRuntime = this.addDispose(new CodeRuntime(options));
this.addDispose(
this.schemaService.onSchemaUpdate(({ key, data }) => {
if (key === 'constants') {
Expand All @@ -39,10 +42,10 @@ export class CodeRuntimeService extends Disposable implements ICodeRuntimeServic
createCodeRuntime<T extends StringDictionary = StringDictionary>(
options: CodeRuntimeOptions<T> = {},
): ICodeRuntime<T> {
invariant(this.rootRuntime, `please initialize codeRuntimeService on renderer starting!`);
this._throwIfDisposed();

return options.parentScope
? new CodeRuntime(options)
: this.rootRuntime.createChild<T>(options);
return this.addDispose(
options.parentScope ? new CodeRuntime(options) : this.rootRuntime.createChild<T>(options),
);
}
}
52 changes: 40 additions & 12 deletions packages/renderer-core/src/services/code-runtime/codeScope.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { type StringDictionary, LinkedListNode } from '@alilc/lowcode-shared';
import {
type StringDictionary,
Disposable,
type IDisposable,
LinkedListNode,
} from '@alilc/lowcode-shared';
import { trustedGlobals } from './globals-es2015';

/*
Expand All @@ -9,33 +14,50 @@ const unscopables = trustedGlobals.reduce((acc, key) => ({ ...acc, [key]: true }
__proto__: null,
});

export interface ICodeScope<T extends StringDictionary = StringDictionary> {
export interface ICodeScope<T extends StringDictionary = StringDictionary> extends IDisposable {
readonly value: T;

set(name: keyof T, value: any): void;

setValue(value: Partial<T>, replace?: boolean): void;

createChild<V extends StringDictionary = StringDictionary>(initValue: Partial<V>): ICodeScope<V>;

dispose(): void;
}

export class CodeScope<T extends StringDictionary = StringDictionary> implements ICodeScope<T> {
export class CodeScope<T extends StringDictionary = StringDictionary>
extends Disposable
implements ICodeScope<T>
{
node = LinkedListNode.Undefined;

private proxyValue: T;
private _proxyValue?: T;

constructor(initValue: Partial<T>) {
super();

this.node.current = initValue;
this.proxyValue = this.createProxy();
}

get value(): T {
return this.proxyValue;
this._throwIfDisposed('code scope has been disposed');

if (!this._proxyValue) {
this._proxyValue = this._createProxy();
}
return this._proxyValue;
}

set(name: keyof T, value: any): void {
this._throwIfDisposed('code scope has been disposed');

this.node.current[name] = value;
}

setValue(value: Partial<T>, replace = false) {
this._throwIfDisposed('code scope has been disposed');

if (replace) {
this.node.current = { ...value };
} else {
Expand All @@ -44,24 +66,30 @@ export class CodeScope<T extends StringDictionary = StringDictionary> implements
}

createChild<V extends StringDictionary = StringDictionary>(initValue: Partial<V>): ICodeScope<V> {
const childScope = new CodeScope(initValue);
const childScope = this.addDispose(new CodeScope(initValue));
childScope.node.prev = this.node;

return childScope;
}

private createProxy(): T {
dispose(): void {
super.dispose();
this.node = LinkedListNode.Undefined;
this._proxyValue = undefined;
}

private _createProxy(): T {
return new Proxy(Object.create(null) as T, {
set: (target, p, newValue) => {
this.set(p as string, newValue);
return true;
},
get: (_, p) => this.findValue(p) ?? undefined,
has: (_, p) => this.hasProperty(p),
get: (_, p) => this._findValue(p) ?? undefined,
has: (_, p) => this._hasProperty(p),
});
}

private findValue(prop: PropertyKey) {
private _findValue(prop: PropertyKey) {
if (prop === Symbol.unscopables) return unscopables;

let node = this.node;
Expand All @@ -73,7 +101,7 @@ export class CodeScope<T extends StringDictionary = StringDictionary> implements
}
}

private hasProperty(prop: PropertyKey): boolean {
private _hasProperty(prop: PropertyKey): boolean {
if (prop in unscopables) return true;

let node = this.node;
Expand Down
1 change: 1 addition & 0 deletions packages/renderer-core/src/services/code-runtime/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './codeScope';
export * from './codeRuntimeService';
export * from './codeRuntime';
export * from './value';
File renamed without changes.
Loading

0 comments on commit d4bdf14

Please sign in to comment.