##// END OF EJS Templates
WIP observables
WIP observables

File last commit:

r146:af4f8424e83d v1.9.0 default
r157:f9518367061a default
Show More
render.ts
334 lines | 10.5 KiB | video/mp2t | TypeScriptLexer
cin
Testing nested watch, release candidate
r101 import { TraceSource } from "@implab/core-amd/log/TraceSource";
import { id as mid } from "module";
cin
`Subscribable` is made compatible with rxjs, added map, filter and scan...
r102 import { IScope, Scope } from "./Scope";
import { isNode, isRendition, isWidget } from "./traits";
cin
Testing nested watch, release candidate
r101
const trace = TraceSource.get(mid);
cin
file rename
r98
cin
`Subscribable` is made compatible with rxjs, added map, filter and scan...
r102 interface Context {
cin
added whenRendered() method to wait for pending oprations to complete
r118 readonly scope: IScope;
cin
file rename
r98
cin
Added priorities to render tasks, revisited rendering scheduler...
r146 readonly priority: number;
cin
added whenRendered() method to wait for pending oprations to complete
r118 readonly hooks?: (() => void)[];
cin
`Subscribable` is made compatible with rxjs, added map, filter and scan...
r102 }
cin
Testing nested watch, release candidate
r101
cin
Added priorities to render tasks, revisited rendering scheduler...
r146 type RenderTask = {
/**
* The priority for this task
*/
readonly priority: number,
/**
* The rendering action performed in this task
*/
readonly render: () => void;
};
type Range = {
/** minimum value in this range */
readonly min: number;
/** maximum value in this range */
readonly max: number;
cin
linting
r109 };
cin
Testing nested watch, release candidate
r101
cin
Added priorities to render tasks, revisited rendering scheduler...
r146 // empty range
const emptyPriorities: Range = { min: NaN, max: NaN };
// holds render tasks
let _renderQueue: RenderTask[] = [];
// holds the minimum and the maximum task priorities in the queue. Used to
// optimize rendering process is all tasks are with the same priority.
let _renderQueuePriorities: Range = emptyPriorities;
// current context
let _context: Context = {
scope: Scope.dummy,
priority: 0
};
// started render operations
cin
added whenRendered() method to wait for pending oprations to complete
r118 let _renderCount = 0;
cin
Added priorities to render tasks, revisited rendering scheduler...
r146
// next id for render operations
cin
added whenRendered() method to wait for pending oprations to complete
r118 let _renderId = 1;
cin
Added priorities to render tasks, revisited rendering scheduler...
r146
// hooks for render completion, executed when all render operations has
// been completed
cin
added whenRendered() method to wait for pending oprations to complete
r118 let _renderedHooks: (() => void)[] = [];
cin
Added priorities to render tasks, revisited rendering scheduler...
r146 const guard = (cb: () => void) => {
cin
Testing nested watch, release candidate
r101 try {
cin
Added priorities to render tasks, revisited rendering scheduler...
r146 cb();
cin
Testing nested watch, release candidate
r101 } catch (e) {
trace.error(e);
}
cin
linting
r109 };
cin
file rename
r98
cin
added whenRendered() method to wait for pending oprations to complete
r118 /**
cin
Added priorities to render tasks, revisited rendering scheduler...
r146 * Creates a new rendition context with the specified parameters and makes it
* an active context.
cin
added whenRendered() method to wait for pending oprations to complete
r118 *
cin
Added priorities to render tasks, revisited rendering scheduler...
r146 * @see getScope
* @see getPriority
cin
added whenRendered() method to wait for pending oprations to complete
r118 *
cin
Added priorities to render tasks, revisited rendering scheduler...
r146 * @param scope The scope for the current context
* @param priority The priority for the current context
* @returns The function to restore the previous context and execute pending hooks
cin
added whenRendered() method to wait for pending oprations to complete
r118 */
cin
Added priorities to render tasks, revisited rendering scheduler...
r146 const enterContext = (scope: IScope, priority: number) => {
const prev = _context;
const captured = _context = { scope, priority, hooks: [] };
return () => {
if (_context !== captured)
trace.error("endRender mismatched beginRender call");
cin
added whenRendered() method to wait for pending oprations to complete
r118
cin
Added priorities to render tasks, revisited rendering scheduler...
r146 const { hooks } = _context;
if (hooks)
hooks.forEach(guard);
cin
Fixed scheduleRender context restoration
r122
cin
Added priorities to render tasks, revisited rendering scheduler...
r146 _context = prev;
cin
added whenRendered() method to wait for pending oprations to complete
r118 };
cin
linting
r109 };
cin
Testing nested watch, release candidate
r101
/**
cin
Added priorities to render tasks, revisited rendering scheduler...
r146 * Starts the new render operation. When the operation is started the counter
* of running operations is increased. If this is the first render operation
* then the `onRendering` event is fired.
*
* @returns The id of the started rendering operation.
cin
Testing nested watch, release candidate
r101 */
cin
Added priorities to render tasks, revisited rendering scheduler...
r146 const startRender = () => {
_renderCount++;
if (_renderCount === 1)
onRendering();
cin
added whenRendered() method to wait for pending oprations to complete
r118
cin
Added priorities to render tasks, revisited rendering scheduler...
r146 return _renderId++;
};
cin
`Subscribable` is made compatible with rxjs, added map, filter and scan...
r102
cin
Added priorities to render tasks, revisited rendering scheduler...
r146 /**
* Completes the rendering operation. When the operation is completed the counter
* of running operations is decreased. If there is no more running operations left
* then the `onRendered` event is fired.
*
*/
const completeRender = () => {
cin
added whenRendered() method to wait for pending oprations to complete
r118 _renderCount--;
if (_renderCount === 0)
onRendered();
cin
linting
r109 };
cin
Testing nested watch, release candidate
r101
cin
Added priorities to render tasks, revisited rendering scheduler...
r146 /**
* Invokes the specified within the rendition context. The rendition context
* is created for the task invocation and restored after the task is competed.
*
* @param scope The cope for the rendition context
* @param priority The priority for the rendition context
* @returns The result returned by the task
*/
export const renderTask = <T>(task: () => T, scope: IScope, priority: number, renderId: number) => {
const restoreContext = enterContext(scope, priority);
try {
trace.debug("beginRender [{0}], priority = {1}", renderId, priority);
return task();
} finally {
trace.debug("endRender [{0}]", renderId);
restoreContext();
completeRender();
}
};
const processRenderQueue = () => {
const q = _renderQueue;
const { min, max } = _renderQueuePriorities;
_renderQueue = [];
_renderQueuePriorities = emptyPriorities;
// all tasks scheduled due queue processing will be queued to the next
// processRenderQueue iteration
if (min !== max) {
// if there are tasks with different priorities in the queue
trace.debug("Processing render queue, {0} tasks, priorities=[{1}..{2}] ", q.length, min, max);
q.sort(({ priority: a }, { priority: b }) => a - b).forEach(({ render }) => guard(render));
} else {
// all tasks are have same priority
trace.debug("Processing render queue, {0} tasks, priority = {1} ", q.length, min);
q.forEach(({ render }) => guard(render));
}
if (_renderQueue.length)
trace.debug("Render queue is processed, {0} tasks rescheduled", _renderQueue.length);
};
/**
* Adds the specified task to the render queue. The task will be added with the
* specified priority.
*
* Render queue contains a list of render tasks. Each task is executed within
* its own rendering context with the specified scope. The priority determines
* the order in which tasks will be executed where the tasks with lower priority
* numbers are executed first.
*
* When the queue is empty and the task is added then the render queue will be
* scheduled for execution. While the current queue is being executed all
* enqueued tasks are added to the new queue and processed after the current
* execution has been completed.
*
* @param task The action to execute. This action will be executed with
* {@link renderTask} function in its own rendering context.
* @param scope The scope used to create a rendering context for the task.
* @param priority The priority
*/
export const queueRenderTask = (task: () => void, scope = Scope.dummy, priority = getPriority()) => {
const renderId = startRender();
trace.debug("scheduleRender [{0}], priority = {1}", renderId, priority);
const render = () => renderTask(task, scope, priority, renderId);
if (!_renderQueue.length) {
// this is the first task, schedule next render queue processing
Promise.resolve().then(processRenderQueue, e => trace.error(e));
// initialize priorities
_renderQueuePriorities = { min: priority, max: priority };
} else {
// update priorities if needed
const { min, max } = _renderQueuePriorities;
if (priority < min)
_renderQueuePriorities = { min: priority, max };
else if (priority > max)
_renderQueuePriorities = { min, max: priority };
}
_renderQueue.push({ priority, render });
};
/**
* Starts the synchronous rendering process with the specified scope and priority.
*
* @param scope The scope for the current rendition
* @param priority The priority for the current scope
* @returns The function to complete the current rendering
*/
export const beginRender = (scope = Scope.dummy, priority = 0) => {
const renderId = startRender();
const restoreContext = enterContext(scope, priority);
trace.debug("beginRender [{0}], priority = {1}", renderId, priority);
return () => {
trace.debug("endRender [{0}]", renderId);
restoreContext();
completeRender();
};
};
cin
added whenRendered() method to wait for pending oprations to complete
r118 // called when the first beginRender is called for this iteration
const onRendering = () => {
cin
added 'buffer' and 'subject' observable operators
r133 trace.log("Rendering started");
cin
added whenRendered() method to wait for pending oprations to complete
r118 setTimeout(() => {
if (_renderCount !== 0)
trace.error("Rendering tasks aren't finished, currently running = {0}", _renderCount);
});
};
// called when all render operations are complete
const onRendered = () => {
cin
added 'buffer' and 'subject' observable operators
r133 trace.log("Rendering compete");
cin
added whenRendered() method to wait for pending oprations to complete
r118 _renderedHooks.forEach(guard);
_renderedHooks = [];
};
cin
Added priorities to render tasks, revisited rendering scheduler...
r146 /** Returns promise when the rendering has been completed. */
cin
added whenRendered() method to wait for pending oprations to complete
r118 export const whenRendered = () => new Promise<void>((resolve) => {
if (_renderCount)
_renderedHooks.push(resolve);
else
resolve();
});
cin
Added priorities to render tasks, revisited rendering scheduler...
r146 /**
* Registers hook which is called after the render operation is completed. The
* hook will be called once only for the current operation.
*
* @param hook The hook which should be called when rendering is complete.
*/
cin
Testing nested watch, release candidate
r101 export const renderHook = (hook: () => void) => {
cin
`Subscribable` is made compatible with rxjs, added map, filter and scan...
r102 const { hooks } = _context;
if (hooks)
cin
Testing nested watch, release candidate
r101 hooks.push(hook);
else
guard(hook);
cin
linting
r109 };
cin
file rename
r98
cin
Added priorities to render tasks, revisited rendering scheduler...
r146 /**
* Registers special hook which will be called with the specified state. The
* hook is called once after the rendering is complete. When the rendition is
* destroyed the hook is called with the undefined parameter.
*
* This function is used to register `ref` hooks form a tsx rendition.
*
* @param value The state which will be supplied as a parameter for the hook
* @param ref reference hook
*/
cin
`Subscribable` is made compatible with rxjs, added map, filter and scan...
r102 export const refHook = <T>(value: T, ref: JSX.Ref<T>) => {
const { hooks, scope } = _context;
if (hooks)
hooks.push(() => ref(value));
else
guard(() => ref(value));
scope.own(() => ref(undefined));
cin
linting
r109 };
cin
`Subscribable` is made compatible with rxjs, added map, filter and scan...
r102
cin
Added priorities to render tasks, revisited rendering scheduler...
r146 /** Returns the current scope. Scope is used to track resources bound to the
* current rendering. When the rendering is destroyed the scope is cleaned and
* all bound resources are released.
*/
cin
`Subscribable` is made compatible with rxjs, added map, filter and scan...
r102 export const getScope = () => _context.scope;
cin
file rename
r98
cin
Added priorities to render tasks, revisited rendering scheduler...
r146 /**
* Returns the current render task priority. This value is used by some renditions
* to schedule asynchronous nested updates with lower priority then themselves.
*/
export const getPriority = () => _context.priority;
cin
file rename
r98 /** Schedules the rendition to be rendered to the DOM Node
* @param rendition The rendition to be rendered
* @param scope The scope
*/
cin
Added priorities to render tasks, revisited rendering scheduler...
r146 export const render = (rendition: unknown, scope = Scope.dummy) =>
renderTask(() => getItemDom(rendition), scope, getPriority(), startRender());
cin
file rename
r98
cin
type fixes for i18n, store
r120 const emptyFragment = document.createDocumentFragment();
cin
file rename
r98 /** Renders DOM element for different types of the argument. */
cin
Testing nested watch, release candidate
r101 export const getItemDom = (v: unknown) => {
cin
file rename
r98 if (typeof v === "string" || typeof v === "number" || v instanceof RegExp || v instanceof Date) {
// primitive types converted to the text nodes
return document.createTextNode(v.toString());
} else if (isNode(v)) {
// nodes are kept as is
return v;
} else if (isRendition(v)) {
// renditions are instantiated
cin
Testing nested watch, release candidate
r101 return v.getDomNode();
cin
file rename
r98 } else if (isWidget(v)) {
// widgets are converted to it's markup
return v.domNode;
} else if (typeof v === "boolean" || v === null || v === undefined) {
cin
`Subscribable` is made compatible with rxjs, added map, filter and scan...
r102 // null | undefined | boolean are removed
cin
type fixes for i18n, store
r120 return emptyFragment;
cin
file rename
r98 } else if (v instanceof Array) {
// arrays will be translated to document fragments
const fragment = document.createDocumentFragment();
cin
Testing nested watch, release candidate
r101 v.map(item => getItemDom(item))
cin
file rename
r98 .forEach(node => fragment.appendChild(node));
return fragment;
} else {
// bug: explicit error otherwise
cin
linting
r109 throw new Error(`Invalid parameter: ${String(v)}`);
cin
file rename
r98 }
cin
linting
r109 };