add doc comments
This commit is contained in:
parent
bb152d6145
commit
5b46dcae99
|
|
@ -1,243 +0,0 @@
|
||||||
/**
|
|
||||||
* A function that processes the event and updates the target.
|
|
||||||
* @param {EventTarget} source - The source that emitted the event
|
|
||||||
* @param {EventTarget} target - The target that receives propagated changes
|
|
||||||
* @param {Event} event - The event that triggered the propagation
|
|
||||||
* @returns {any} - The result of the propagation
|
|
||||||
*/
|
|
||||||
export type PropagatorFunction = (source: EventTarget, target: EventTarget, event: Event) => any;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A parser function that converts a string expression into a PropagatorFunction.
|
|
||||||
* @param {string} body - The string expression to parse
|
|
||||||
* @param {Propagator} [propagator] - The Propagator instance (optional)
|
|
||||||
* @returns {PropagatorFunction | null} - The parsed PropagatorFunction or null if parsing fails
|
|
||||||
*/
|
|
||||||
export type PropagatorParser = (body: string, propagator?: Propagator) => PropagatorFunction | null;
|
|
||||||
|
|
||||||
export type PropagatorOptions = {
|
|
||||||
source?: EventTarget | null;
|
|
||||||
target?: EventTarget | null;
|
|
||||||
event?: string | null;
|
|
||||||
handler?: PropagatorFunction | string;
|
|
||||||
parser?: PropagatorParser;
|
|
||||||
onParseSuccess?: (body: string) => void;
|
|
||||||
onParseError?: (error: Error) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A propagator takes in a source and target and listens for events on the source.
|
|
||||||
* When an event is detected, it will execute a handler and update the target.
|
|
||||||
*/
|
|
||||||
export class Propagator {
|
|
||||||
#source: EventTarget | null = null;
|
|
||||||
#target: EventTarget | null = null;
|
|
||||||
#eventName: string | null = null;
|
|
||||||
#handler: PropagatorFunction | null = null;
|
|
||||||
|
|
||||||
#parser: PropagatorParser | null = null;
|
|
||||||
#onParse: ((body: string) => void) | null = null;
|
|
||||||
#onError: ((error: Error) => void) | null = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new Propagator instance.
|
|
||||||
* @param {PropagatorOptions} options - Configuration options for the propagator
|
|
||||||
* @param {EventTarget} [options.source] - Source that emits events
|
|
||||||
* @param {EventTarget} [options.target] - Target that receives propagated changes
|
|
||||||
* @param {string} [options.event] - Event name to listen for on the source
|
|
||||||
* @param {PropagatorFunction|string} [options.handler] - Event handler function or string expression
|
|
||||||
* @param {PropagatorParser} [options.parser] - Custom parser for string handlers
|
|
||||||
* @param {Function} [options.onParse] - Callback fired when a string handler is parsed
|
|
||||||
* @param {Function} [options.onError] - Callback fired when an error occurs during parsing
|
|
||||||
*/
|
|
||||||
constructor(options: PropagatorOptions = {}) {
|
|
||||||
const {
|
|
||||||
source = null,
|
|
||||||
target = null,
|
|
||||||
event = null,
|
|
||||||
handler = null,
|
|
||||||
onParseSuccess: onParse = null,
|
|
||||||
onParseError: onError = null,
|
|
||||||
parser = null,
|
|
||||||
} = options;
|
|
||||||
|
|
||||||
this.#onParse = onParse;
|
|
||||||
this.#onError = onError;
|
|
||||||
this.#parser = parser;
|
|
||||||
this.source = source;
|
|
||||||
this.target = target;
|
|
||||||
if (event) this.event = event;
|
|
||||||
if (handler) this.handler = handler;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The source that emits events.
|
|
||||||
* Setting a new source will automatically update event listeners.
|
|
||||||
*/
|
|
||||||
get source(): EventTarget | null {
|
|
||||||
return this.#source;
|
|
||||||
}
|
|
||||||
|
|
||||||
set source(eventTarget: EventTarget | null) {
|
|
||||||
// Remove listener from old source
|
|
||||||
if (this.#source && this.#eventName) {
|
|
||||||
this.#source.removeEventListener(this.#eventName, this.#handleEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.#source = eventTarget;
|
|
||||||
|
|
||||||
// Add listener to new source
|
|
||||||
if (this.#source && this.#eventName) {
|
|
||||||
this.#source.addEventListener(this.#eventName, this.#handleEvent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The target that receives propagated changes.
|
|
||||||
*/
|
|
||||||
get target(): EventTarget | null {
|
|
||||||
return this.#target;
|
|
||||||
}
|
|
||||||
|
|
||||||
set target(eventTarget: EventTarget | null) {
|
|
||||||
this.#target = eventTarget;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The name of the event to listen for on the source.
|
|
||||||
* Setting a new event name will automatically update event listeners.
|
|
||||||
*/
|
|
||||||
get event(): string | null {
|
|
||||||
return this.#eventName;
|
|
||||||
}
|
|
||||||
|
|
||||||
set event(name: string) {
|
|
||||||
// Remove old listener
|
|
||||||
if (this.#source && this.#eventName) {
|
|
||||||
this.#source.removeEventListener(this.#eventName, this.#handleEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.#eventName = name;
|
|
||||||
|
|
||||||
// Add new listener
|
|
||||||
if (this.#source && this.#eventName) {
|
|
||||||
this.#source.addEventListener(this.#eventName, this.#handleEvent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The handler function that processes the event and updates the target.
|
|
||||||
* Can be set using either a function or a string expression.
|
|
||||||
*/
|
|
||||||
get handler(): PropagatorFunction | null {
|
|
||||||
return this.#handler;
|
|
||||||
}
|
|
||||||
|
|
||||||
set handler(value: PropagatorFunction | string | null) {
|
|
||||||
if (typeof value === 'string') {
|
|
||||||
try {
|
|
||||||
this.#handler = this.#parser ? this.#parser(value, this) : this.#defaultParser(value);
|
|
||||||
} catch (error) {
|
|
||||||
this.#handler = null;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.#handler = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Manually triggers the propagation with an optional event.
|
|
||||||
* If no event is provided and an event name is set, creates a new event.
|
|
||||||
* @param {Event} [event] - Optional event to propagate
|
|
||||||
*/
|
|
||||||
propagate(event?: Event): void {
|
|
||||||
if (!event && this.#eventName) {
|
|
||||||
event = new Event(this.#eventName);
|
|
||||||
}
|
|
||||||
if (!event) return;
|
|
||||||
this.#handleEvent(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cleans up the propagator by removing event listeners and clearing references.
|
|
||||||
* Should be called when the propagator is no longer needed.
|
|
||||||
*/
|
|
||||||
dispose(): void {
|
|
||||||
if (this.#source && this.#eventName) {
|
|
||||||
this.#source.removeEventListener(this.#eventName, this.#handleEvent);
|
|
||||||
}
|
|
||||||
this.#source = null;
|
|
||||||
this.#target = null;
|
|
||||||
this.#handler = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
#handleEvent = (event: Event) => {
|
|
||||||
if (!this.#source || !this.#target || !this.#handler) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.#handler(this.#source, this.#target, event);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error in propagator handler:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// This approach turns object syntax into imperative code.
|
|
||||||
// We could alternatively use a parser (i.e. Babel) to statically analyse the code
|
|
||||||
// and check on keystrokes if the code is valid.
|
|
||||||
// We should try a few different things to figure out how this class could work best.
|
|
||||||
#defaultParser = (body: string): PropagatorFunction | null => {
|
|
||||||
const processedExp = body.trim();
|
|
||||||
|
|
||||||
const codeLines: string[] = [];
|
|
||||||
|
|
||||||
// Split the expression into lines, handling different line endings
|
|
||||||
const lines = processedExp.split(/\r?\n/);
|
|
||||||
|
|
||||||
for (const line of lines) {
|
|
||||||
let line_trimmed = line.trim();
|
|
||||||
if (!line_trimmed) continue;
|
|
||||||
|
|
||||||
// Remove trailing comma if it exists (only if it's at the very end of the line)
|
|
||||||
if (line_trimmed.endsWith(',')) {
|
|
||||||
line_trimmed = line_trimmed.slice(0, -1).trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the first colon index, which separates the key and value.
|
|
||||||
// Colons can still be used in ternary operators or other expressions,
|
|
||||||
const colonIndex = line_trimmed.indexOf(':');
|
|
||||||
if (colonIndex === -1) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const key = line_trimmed.slice(0, colonIndex).trim();
|
|
||||||
const value = line_trimmed.slice(colonIndex + 1).trim();
|
|
||||||
|
|
||||||
if (key === '()') {
|
|
||||||
// Anonymous function: directly evaluate the value
|
|
||||||
codeLines.push(`${value};`);
|
|
||||||
} else if (key.endsWith('()')) {
|
|
||||||
// If the key is a method, execute it if the condition is true
|
|
||||||
const methodName = key.slice(0, -2);
|
|
||||||
codeLines.push(`
|
|
||||||
if (typeof to.${methodName} !== 'function') throw new Error(\`Method '${methodName}' does not exist on the target.\`);
|
|
||||||
else if (${value}) to.${methodName}();`);
|
|
||||||
} else {
|
|
||||||
// For property assignments, assign the value directly
|
|
||||||
codeLines.push(`
|
|
||||||
if (!('${key}' in to)) throw new Error(\`Property '${key}' does not exist on the target.\`);
|
|
||||||
to.${key} = ${value};`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const functionBody = codeLines.join('\n');
|
|
||||||
|
|
||||||
try {
|
|
||||||
const handler = new Function('from', 'to', 'event', functionBody) as PropagatorFunction;
|
|
||||||
this.#onParse?.(functionBody);
|
|
||||||
return handler;
|
|
||||||
} catch (error) {
|
|
||||||
this.#onError?.(error as Error);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -21,7 +21,6 @@ export class ClientRectObserver {
|
||||||
#rootRect = this.#root.getBoundingClientRect();
|
#rootRect = this.#root.getBoundingClientRect();
|
||||||
|
|
||||||
#entries: ClientRectObserverEntry[] = [];
|
#entries: ClientRectObserverEntry[] = [];
|
||||||
#rafId = -1;
|
|
||||||
|
|
||||||
#callback: ClientRectObserverCallback;
|
#callback: ClientRectObserverCallback;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,20 @@
|
||||||
import { ReactiveElement } from '@lit/reactive-element';
|
import { ReactiveElement } from '@lit/reactive-element';
|
||||||
|
|
||||||
// will eventually extend Lit's ReactiveElement
|
/**
|
||||||
|
* Base class for all custom elements. Extends Lit's `ReactiveElement` and adds some utilities for defining the element.
|
||||||
|
* ```ts
|
||||||
|
* class MyElement extends FolkElement {
|
||||||
|
* static tagName = 'my-element';
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* MyElement.define();
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
export class FolkElement extends ReactiveElement {
|
export class FolkElement extends ReactiveElement {
|
||||||
|
/** Defines the name of the custom element, must include a hyphen or it will error out when defined. */
|
||||||
static tagName = '';
|
static tagName = '';
|
||||||
|
|
||||||
|
/** Defines the custom element with the global CustomElementRegistry, ignored if called more than once. Errors if no tagName is defined or it doesn't include a hyphen. */
|
||||||
static define() {
|
static define() {
|
||||||
if (customElements.get(this.tagName)) return;
|
if (customElements.get(this.tagName)) return;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// this needs to move to the web extension once that's set up.
|
||||||
|
|
||||||
import { ClientRectObserverEntry } from './client-rect-observer.ts';
|
import { ClientRectObserverEntry } from './client-rect-observer.ts';
|
||||||
import { FolkObserver } from './folk-observer.ts';
|
import { FolkObserver } from './folk-observer.ts';
|
||||||
|
|
||||||
|
|
|
||||||
10
lib/rAF.ts
10
lib/rAF.ts
|
|
@ -1,3 +1,10 @@
|
||||||
|
/**
|
||||||
|
* The nature of this project requires that we have multiple game/simulation loops running at the same time.
|
||||||
|
* This usually means multiple calls to window.requestAnimationFrame, which turns out is very costly.
|
||||||
|
* These helper functions batch calls to window.requestAnimationFrame to avoid that overhead.
|
||||||
|
* TODO: add some benchmarks for this
|
||||||
|
*/
|
||||||
|
|
||||||
const callbacks = new Set<FrameRequestCallback>();
|
const callbacks = new Set<FrameRequestCallback>();
|
||||||
let rAFId = -1;
|
let rAFId = -1;
|
||||||
|
|
||||||
|
|
@ -8,7 +15,7 @@ function onRAF(time: DOMHighResTimeStamp) {
|
||||||
values.forEach((callback) => callback(time));
|
values.forEach((callback) => callback(time));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Batch multiple callbacks into a single rAF for better performance
|
/** Batch calls to window.requestAnimationFrame */
|
||||||
export function requestAnimationFrame(callback: FrameRequestCallback) {
|
export function requestAnimationFrame(callback: FrameRequestCallback) {
|
||||||
if (callbacks.size === 0) {
|
if (callbacks.size === 0) {
|
||||||
rAFId = window.requestAnimationFrame(onRAF);
|
rAFId = window.requestAnimationFrame(onRAF);
|
||||||
|
|
@ -17,6 +24,7 @@ export function requestAnimationFrame(callback: FrameRequestCallback) {
|
||||||
callbacks.add(callback);
|
callbacks.add(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Batch calls to window.cancelAnimationFrame */
|
||||||
export function cancelAnimationFrame(callback: FrameRequestCallback) {
|
export function cancelAnimationFrame(callback: FrameRequestCallback) {
|
||||||
callbacks.delete(callback);
|
callbacks.delete(callback);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@
|
||||||
|
|
||||||
function updateFlowerProximity(flower) {
|
function updateFlowerProximity(flower) {
|
||||||
const alreadyIntersection = proximitySet.has(flower);
|
const alreadyIntersection = proximitySet.has(flower);
|
||||||
// TODO: refactor this hack once resizing and the vertices API are figured out
|
|
||||||
const isNowIntersecting = aabbIntersection(
|
const isNowIntersecting = aabbIntersection(
|
||||||
recordPlayerGeometry.getTransformDOMRect(),
|
recordPlayerGeometry.getTransformDOMRect(),
|
||||||
flower.getTransformDOMRect(),
|
flower.getTransformDOMRect(),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue