packages/core/src/features/physics/lib/2d/canvas/custom.ts
This is the source snapshot used to build these API details. View this revision on GitHub.
1 import type { PibblPrimitiveComponent } from '@pibbl/core';
2 import {
3 pibblInternalRunPurePrimitiveResolver,
4 derivePrimitive,
5 } from '@pibbl/core/internal';
6 import type { PibblPhysicsShape2D } from '../../../2d-geometry.js';
7 import type {
8 PibblPhysicsAttachment2D,
9 PibblPhysicsPrimitiveProps2D,
10 } from '../types.js';
11 import { requireShapeRecord2D } from '../shape-records.js';
12 import { installCustomShapeResolver2D } from './inference.js';
13
14 /**
15 * Adds physics-attachment support to a custom primitive using its resolved-style shape callback.
16 *
17 * @param component - Primitive whose rendered style determines its collision shape. See
18 * {@link PibblPrimitiveComponent} .
19 * @param options - Pure resolver deriving collision geometry from resolved style. See
20 * {@link PibblPhysicsShape2D} .
21 * @returns A derived primitive accepting the public physics attachment prop. See
22 * {@link PibblPrimitiveComponent} , {@link PibblPhysicsPrimitiveProps2D} ,
23 * {@link PibblPhysicsAttachment2D} .
24 *
25 * @see {@link PibblPrimitiveComponent}
26 * @see {@link PibblPhysicsShape2D}
27 * @see {@link PibblPhysicsPrimitiveProps2D}
28 * @see {@link PibblPhysicsAttachment2D}
29 */
30 export function withPhysicsShape2D<P, I, ResolvedStyle>(
31 component: PibblPrimitiveComponent<P, I>,
32 options: Readonly<{
33 resolveShape(style: Readonly<ResolvedStyle>): PibblPhysicsShape2D;
34 }>,
35 ): PibblPrimitiveComponent<
36 P & PibblPhysicsPrimitiveProps2D,
37 I & { readonly physics?: PibblPhysicsAttachment2D }
38 > {
39 if (typeof options.resolveShape !== 'function') {
40 throw new TypeError('withPhysicsShape2D options.resolveShape must be a function.');
41 }
42 const derived = derivePrimitive(component) as PibblPrimitiveComponent<
43 P & PibblPhysicsPrimitiveProps2D,
44 I & { readonly physics?: PibblPhysicsAttachment2D }
45 >;
46 installCustomShapeResolver2D(derived, style => {
47 const snapshot = immutableSnapshot(style) as Readonly<ResolvedStyle>;
48 const result = pibblInternalRunPurePrimitiveResolver(() =>
49 options.resolveShape(snapshot)
50 );
51 if (isThenable(result)) {
52 throw new TypeError('A physics 2D shape resolver must return synchronously, not a Promise.');
53 }
54 try {
55 requireShapeRecord2D(result);
56 } catch {
57 throw new TypeError('A physics 2D shape resolver must return a Pibbl physics 2D shape.');
58 }
59 return result;
60 });
61 return derived;
62 }
63
64 function immutableSnapshot(value: unknown): unknown {
65 if (Array.isArray(value)) return Object.freeze(value.map(immutableSnapshot));
66 if (typeof value !== 'object' || value === null) return value;
67 if (
68 (typeof Path2D !== 'undefined' && value instanceof Path2D) ||
69 (typeof CanvasGradient !== 'undefined' && value instanceof CanvasGradient) ||
70 (typeof CanvasPattern !== 'undefined' && value instanceof CanvasPattern)
71 ) return value;
72 const copy: Record<PropertyKey, unknown> = {};
73 for (const key of Reflect.ownKeys(value)) {
74 copy[key] = immutableSnapshot((value as Record<PropertyKey, unknown>)[key]);
75 }
76 return Object.freeze(copy);
77 }
78
79 function isThenable(value: unknown): value is PromiseLike<unknown> {
80 return typeof value === 'object' && value !== null &&
81 typeof (value as { then?: unknown }).then === 'function';
82 }
83
Documentation version
Section titled “Documentation version”Documentation built with @pibbl/core 0.0.2, revision 2dccb19. ALPHA — NOT FOR PRODUCTION USE.