Skip to content

packages/core/src/features/physics/lib/2d/canvas/custom.ts

Read as Markdown

This is the source snapshot used to build these API details. View this revision on GitHub.

Back to reference

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 built with @pibbl/core 0.0.2, revision 272a94a. ALPHA — NOT FOR PRODUCTION USE.