@multiplekex/shallot 0.1.4 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@multiplekex/shallot",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -13,7 +13,7 @@
13
13
  "rust/transforms/pkg"
14
14
  ],
15
15
  "scripts": {
16
- "build:wasm": "./rust/transforms/build.sh",
16
+ "build:wasm": "bash ./rust/transforms/build.sh",
17
17
  "test": "bun test"
18
18
  },
19
19
  "dependencies": {
@@ -2,6 +2,7 @@ import { State } from "./state";
2
2
  import type { Plugin, Loading } from "./types";
3
3
  import { toposort, type System } from "./scheduler";
4
4
  import { registerComponent } from "./component";
5
+ import { initRuntime } from "./runtime";
5
6
 
6
7
  export class StateBuilder {
7
8
  static defaultPlugins: readonly Plugin[] = [];
@@ -118,7 +119,6 @@ export class StateBuilder {
118
119
  }
119
120
 
120
121
  if (this._scenes.length > 0) {
121
- const { initRuntime } = await import("./runtime");
122
122
  const { loadSceneFile } = await import("./xml");
123
123
  const runtime = await initRuntime();
124
124
  for (const scenePath of this._scenes) {
@@ -1,10 +1,16 @@
1
1
  import { setTraits } from "../../core/component";
2
- import { clamp, lookAt, type State, type System, type Plugin } from "../../core";
2
+ import { clamp, lookAt, type State, type System, type Plugin, type MouseState } from "../../core";
3
3
  import { Transform } from "../../standard/transforms";
4
4
  import { Input, InputPlugin } from "../../standard/input";
5
5
 
6
6
  const Tau = Math.PI * 2;
7
7
 
8
+ export const OrbitButton = {
9
+ Left: 0,
10
+ Middle: 1,
11
+ Right: 2,
12
+ } as const;
13
+
8
14
  export const Orbit = {
9
15
  target: [] as number[],
10
16
  yaw: [] as number[],
@@ -20,6 +26,7 @@ export const Orbit = {
20
26
  smoothness: [] as number[],
21
27
  sensitivity: [] as number[],
22
28
  zoomSpeed: [] as number[],
29
+ button: [] as number[],
23
30
  };
24
31
 
25
32
  setTraits(Orbit, {
@@ -38,6 +45,7 @@ setTraits(Orbit, {
38
45
  smoothness: 0.3,
39
46
  sensitivity: 0.005,
40
47
  zoomSpeed: 0.025,
48
+ button: OrbitButton.Right,
41
49
  }),
42
50
  });
43
51
 
@@ -54,6 +62,12 @@ function angleDiff(from: number, to: number): number {
54
62
  return diff > Math.PI ? diff - Tau : diff;
55
63
  }
56
64
 
65
+ function isOrbitButton(mouse: Readonly<MouseState>, button: number): boolean {
66
+ if (button === OrbitButton.Left) return mouse.left;
67
+ if (button === OrbitButton.Middle) return mouse.middle;
68
+ return mouse.right;
69
+ }
70
+
57
71
  export const OrbitSystem: System = {
58
72
  group: "simulation",
59
73
 
@@ -70,7 +84,7 @@ export const OrbitSystem: System = {
70
84
  const maxDistance = Orbit.maxDistance[eid];
71
85
  const smoothness = Orbit.smoothness[eid];
72
86
 
73
- if (input?.mouse.right) {
87
+ if (input && isOrbitButton(input.mouse, Orbit.button[eid])) {
74
88
  Orbit.targetYaw[eid] -= input.mouse.deltaX * sensitivity;
75
89
  Orbit.targetPitch[eid] = clamp(
76
90
  Orbit.targetPitch[eid] + input.mouse.deltaY * sensitivity,
@@ -1,4 +1,5 @@
1
1
  import { CycleError } from "../../core";
2
+ import { inspect as inspectGraph, type GraphInspection } from "./inspect";
2
3
 
3
4
  export type ResourceId = string;
4
5
  export type NodeId = string;
@@ -162,4 +163,8 @@ export class ComputeGraph {
162
163
  }
163
164
  return this._plan;
164
165
  }
166
+
167
+ inspect(): GraphInspection {
168
+ return inspectGraph(Array.from(this.nodes.values()));
169
+ }
165
170
  }
@@ -2,6 +2,9 @@ import { resource, type Plugin, type State, type System } from "../../core";
2
2
  import { ComputeGraph, type ExecutionContext, type ResourceId } from "./graph";
3
3
 
4
4
  export * from "./graph";
5
+ export * from "./inspect";
6
+ export * from "./readback";
7
+ export * from "./timing";
5
8
 
6
9
  const MIN_CANVAS_SIZE = 1;
7
10
 
@@ -0,0 +1,205 @@
1
+ import type { ComputeNode, NodeId, Phase, ResourceId, ResourceRef } from "./graph";
2
+
3
+ export interface NodeInfo {
4
+ readonly id: NodeId;
5
+ readonly phase: Phase;
6
+ readonly inputs: readonly ResourceRef[];
7
+ readonly outputs: readonly ResourceRef[];
8
+ }
9
+
10
+ export interface EdgeInfo {
11
+ readonly from: NodeId;
12
+ readonly to: NodeId;
13
+ readonly resource: ResourceId;
14
+ }
15
+
16
+ export interface GraphInspection {
17
+ readonly nodes: readonly NodeInfo[];
18
+ readonly edges: readonly EdgeInfo[];
19
+ readonly executionOrder: readonly NodeId[];
20
+ readonly byPhase: ReadonlyMap<Phase, readonly NodeId[]>;
21
+ }
22
+
23
+ const PHASE_ORDER: Phase[] = ["render", "overlay", "postprocess"];
24
+
25
+ export function inspect(nodes: readonly ComputeNode[]): GraphInspection {
26
+ const nodeInfos: NodeInfo[] = nodes.map((n) => ({
27
+ id: n.id,
28
+ phase: n.phase ?? "render",
29
+ inputs: n.inputs,
30
+ outputs: n.outputs,
31
+ }));
32
+
33
+ const producers = new Map<ResourceId, NodeId>();
34
+ for (const n of nodes) {
35
+ for (const out of n.outputs) {
36
+ producers.set(out.id, n.id);
37
+ }
38
+ }
39
+
40
+ const edges: EdgeInfo[] = [];
41
+ for (const n of nodes) {
42
+ for (const inp of n.inputs) {
43
+ const producer = producers.get(inp.id);
44
+ if (producer) {
45
+ edges.push({
46
+ from: producer,
47
+ to: n.id,
48
+ resource: inp.id,
49
+ });
50
+ }
51
+ }
52
+ }
53
+
54
+ const byPhase = new Map<Phase, NodeId[]>();
55
+ for (const phase of PHASE_ORDER) {
56
+ byPhase.set(phase, []);
57
+ }
58
+ for (const n of nodeInfos) {
59
+ byPhase.get(n.phase)!.push(n.id);
60
+ }
61
+
62
+ const executionOrder = topoSortIds(nodes);
63
+
64
+ return { nodes: nodeInfos, edges, executionOrder, byPhase };
65
+ }
66
+
67
+ function topoSortIds(nodes: readonly ComputeNode[]): NodeId[] {
68
+ if (nodes.length === 0) return [];
69
+
70
+ const producers = new Map<ResourceId, ComputeNode>();
71
+ for (const node of nodes) {
72
+ for (const output of node.outputs) {
73
+ producers.set(output.id, node);
74
+ }
75
+ }
76
+
77
+ const adjacency = new Map<ComputeNode, ComputeNode[]>();
78
+ const inDegree = new Map<ComputeNode, number>();
79
+
80
+ for (const node of nodes) {
81
+ adjacency.set(node, []);
82
+ inDegree.set(node, 0);
83
+ }
84
+
85
+ for (const node of nodes) {
86
+ for (const input of node.inputs) {
87
+ const producer = producers.get(input.id);
88
+ if (producer) {
89
+ adjacency.get(producer)!.push(node);
90
+ inDegree.set(node, inDegree.get(node)! + 1);
91
+ }
92
+ }
93
+ }
94
+
95
+ const byPhase = new Map<Phase, ComputeNode[]>();
96
+ for (const phase of PHASE_ORDER) {
97
+ byPhase.set(phase, []);
98
+ }
99
+ for (const node of nodes) {
100
+ const phase = node.phase ?? "render";
101
+ byPhase.get(phase)!.push(node);
102
+ }
103
+
104
+ const sorted: NodeId[] = [];
105
+ for (const phase of PHASE_ORDER) {
106
+ const phaseNodes = byPhase.get(phase)!;
107
+ const phaseInDegree = new Map<ComputeNode, number>();
108
+
109
+ for (const node of phaseNodes) {
110
+ let degree = 0;
111
+ for (const input of node.inputs) {
112
+ const producer = producers.get(input.id);
113
+ if (producer && phaseNodes.includes(producer)) {
114
+ degree++;
115
+ }
116
+ }
117
+ phaseInDegree.set(node, degree);
118
+ }
119
+
120
+ const queue: ComputeNode[] = [];
121
+ for (const node of phaseNodes) {
122
+ if (phaseInDegree.get(node) === 0) {
123
+ queue.push(node);
124
+ }
125
+ }
126
+
127
+ let i = 0;
128
+ while (i < queue.length) {
129
+ const node = queue[i++];
130
+ sorted.push(node.id);
131
+
132
+ for (const dep of phaseNodes) {
133
+ for (const input of dep.inputs) {
134
+ const producer = producers.get(input.id);
135
+ if (producer === node) {
136
+ const newDegree = phaseInDegree.get(dep)! - 1;
137
+ phaseInDegree.set(dep, newDegree);
138
+ if (newDegree === 0) {
139
+ queue.push(dep);
140
+ }
141
+ }
142
+ }
143
+ }
144
+ }
145
+ }
146
+
147
+ return sorted;
148
+ }
149
+
150
+ export function formatGraph(info: GraphInspection): string {
151
+ const lines: string[] = [];
152
+ lines.push("=== Compute Graph ===");
153
+ lines.push("");
154
+
155
+ const nodeMap = new Map(info.nodes.map((n) => [n.id, n]));
156
+
157
+ for (const phase of PHASE_ORDER) {
158
+ const nodeIds = info.byPhase.get(phase) ?? [];
159
+ if (nodeIds.length === 0) continue;
160
+
161
+ lines.push(`[${phase}]`);
162
+ for (const id of nodeIds) {
163
+ const node = nodeMap.get(id)!;
164
+ const ins = node.inputs.map((r) => r.id).join(", ") || "none";
165
+ const outs = node.outputs.map((r) => r.id).join(", ") || "none";
166
+ lines.push(` ${id}: ${ins} -> ${outs}`);
167
+ }
168
+ lines.push("");
169
+ }
170
+
171
+ lines.push("Execution Order:");
172
+ info.executionOrder.forEach((id, i) => {
173
+ lines.push(` ${i + 1}. ${id}`);
174
+ });
175
+
176
+ return lines.join("\n");
177
+ }
178
+
179
+ export function toDot(info: GraphInspection): string {
180
+ const lines: string[] = ["digraph ComputeGraph {"];
181
+ lines.push(" rankdir=LR;");
182
+ lines.push(" node [shape=box];");
183
+ lines.push("");
184
+
185
+ for (const phase of PHASE_ORDER) {
186
+ const nodeIds = info.byPhase.get(phase) ?? [];
187
+ if (nodeIds.length === 0) continue;
188
+
189
+ lines.push(` subgraph cluster_${phase} {`);
190
+ lines.push(` label="${phase}";`);
191
+ for (const id of nodeIds) {
192
+ lines.push(` "${id}";`);
193
+ }
194
+ lines.push(" }");
195
+ }
196
+
197
+ lines.push("");
198
+
199
+ for (const edge of info.edges) {
200
+ lines.push(` "${edge.from}" -> "${edge.to}" [label="${edge.resource}"];`);
201
+ }
202
+
203
+ lines.push("}");
204
+ return lines.join("\n");
205
+ }
@@ -0,0 +1,88 @@
1
+ export async function readBuffer(
2
+ device: GPUDevice,
3
+ source: GPUBuffer,
4
+ size: number
5
+ ): Promise<ArrayBuffer> {
6
+ const staging = device.createBuffer({
7
+ label: "staging-readback",
8
+ size,
9
+ usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,
10
+ });
11
+
12
+ const encoder = device.createCommandEncoder();
13
+ encoder.copyBufferToBuffer(source, 0, staging, 0, size);
14
+ device.queue.submit([encoder.finish()]);
15
+
16
+ await staging.mapAsync(GPUMapMode.READ);
17
+ const data = staging.getMappedRange().slice(0);
18
+ staging.unmap();
19
+ staging.destroy();
20
+
21
+ return data;
22
+ }
23
+
24
+ export async function readFloat32(
25
+ device: GPUDevice,
26
+ buffer: GPUBuffer,
27
+ count: number
28
+ ): Promise<Float32Array> {
29
+ const data = await readBuffer(device, buffer, count * 4);
30
+ return new Float32Array(data);
31
+ }
32
+
33
+ export async function readUint32(
34
+ device: GPUDevice,
35
+ buffer: GPUBuffer,
36
+ count: number
37
+ ): Promise<Uint32Array> {
38
+ const data = await readBuffer(device, buffer, count * 4);
39
+ return new Uint32Array(data);
40
+ }
41
+
42
+ function nextPowerOf2(n: number): number {
43
+ const min = 256;
44
+ return Math.pow(2, Math.ceil(Math.log2(Math.max(n, min))));
45
+ }
46
+
47
+ export class StagingPool {
48
+ private readonly pool = new Map<number, GPUBuffer[]>();
49
+ private readonly device: GPUDevice;
50
+
51
+ constructor(device: GPUDevice) {
52
+ this.device = device;
53
+ }
54
+
55
+ acquire(size: number): GPUBuffer {
56
+ const bucketSize = nextPowerOf2(size);
57
+ const bucket = this.pool.get(bucketSize);
58
+
59
+ if (bucket && bucket.length > 0) {
60
+ return bucket.pop()!;
61
+ }
62
+
63
+ return this.device.createBuffer({
64
+ label: `staging-pool-${bucketSize}`,
65
+ size: bucketSize,
66
+ usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,
67
+ });
68
+ }
69
+
70
+ release(buffer: GPUBuffer): void {
71
+ const bucketSize = buffer.size;
72
+ let bucket = this.pool.get(bucketSize);
73
+ if (!bucket) {
74
+ bucket = [];
75
+ this.pool.set(bucketSize, bucket);
76
+ }
77
+ bucket.push(buffer);
78
+ }
79
+
80
+ dispose(): void {
81
+ for (const bucket of this.pool.values()) {
82
+ for (const buffer of bucket) {
83
+ buffer.destroy();
84
+ }
85
+ }
86
+ this.pool.clear();
87
+ }
88
+ }
@@ -0,0 +1,139 @@
1
+ import type { NodeId } from "./graph";
2
+
3
+ export interface NodeTiming {
4
+ readonly nodeId: NodeId;
5
+ readonly cpuMs: number;
6
+ readonly gpuNs?: number;
7
+ }
8
+
9
+ export interface FrameTiming {
10
+ readonly frameIndex: number;
11
+ readonly nodes: readonly NodeTiming[];
12
+ readonly totalCpuMs: number;
13
+ readonly totalGpuNs?: number;
14
+ }
15
+
16
+ export interface TimingConfig {
17
+ readonly enabled: boolean;
18
+ readonly gpuTimestamps: boolean;
19
+ readonly historySize: number;
20
+ }
21
+
22
+ export interface TimingState {
23
+ readonly config: TimingConfig;
24
+ readonly history: FrameTiming[];
25
+ readonly querySet?: GPUQuerySet;
26
+ readonly resolveBuffer?: GPUBuffer;
27
+ readonly readBuffer?: GPUBuffer;
28
+ }
29
+
30
+ export function supportsTimestampQuery(device: GPUDevice): boolean {
31
+ return device.features.has("timestamp-query");
32
+ }
33
+
34
+ export function createTimingState(
35
+ device: GPUDevice,
36
+ maxNodes: number,
37
+ historySize = 60
38
+ ): TimingState {
39
+ const gpuTimestamps = supportsTimestampQuery(device);
40
+
41
+ let querySet: GPUQuerySet | undefined;
42
+ let resolveBuffer: GPUBuffer | undefined;
43
+ let readBuffer: GPUBuffer | undefined;
44
+
45
+ if (gpuTimestamps) {
46
+ const queryCount = maxNodes * 2;
47
+ querySet = device.createQuerySet({
48
+ label: "timing-queries",
49
+ type: "timestamp",
50
+ count: queryCount,
51
+ });
52
+
53
+ resolveBuffer = device.createBuffer({
54
+ label: "timing-resolve",
55
+ size: queryCount * 8,
56
+ usage: GPUBufferUsage.QUERY_RESOLVE | GPUBufferUsage.COPY_SRC,
57
+ });
58
+
59
+ readBuffer = device.createBuffer({
60
+ label: "timing-read",
61
+ size: queryCount * 8,
62
+ usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,
63
+ });
64
+ }
65
+
66
+ return {
67
+ config: { enabled: true, gpuTimestamps, historySize },
68
+ history: [],
69
+ querySet,
70
+ resolveBuffer,
71
+ readBuffer,
72
+ };
73
+ }
74
+
75
+ export async function readTimestamps(
76
+ device: GPUDevice,
77
+ timing: TimingState,
78
+ nodeCount: number
79
+ ): Promise<BigUint64Array | null> {
80
+ if (!timing.querySet || !timing.resolveBuffer || !timing.readBuffer) {
81
+ return null;
82
+ }
83
+
84
+ const encoder = device.createCommandEncoder();
85
+ encoder.resolveQuerySet(timing.querySet, 0, nodeCount * 2, timing.resolveBuffer, 0);
86
+ encoder.copyBufferToBuffer(timing.resolveBuffer, 0, timing.readBuffer, 0, nodeCount * 2 * 8);
87
+ device.queue.submit([encoder.finish()]);
88
+
89
+ await timing.readBuffer.mapAsync(GPUMapMode.READ);
90
+ const data = new BigUint64Array(timing.readBuffer.getMappedRange().slice(0));
91
+ timing.readBuffer.unmap();
92
+
93
+ return data;
94
+ }
95
+
96
+ export function disposeTimingState(timing: TimingState): void {
97
+ timing.querySet?.destroy();
98
+ timing.resolveBuffer?.destroy();
99
+ timing.readBuffer?.destroy();
100
+ }
101
+
102
+ export interface TimingCollector {
103
+ readonly nodeTimings: Map<NodeId, { start: number; end: number }>;
104
+ beginNode(nodeId: NodeId): void;
105
+ endNode(nodeId: NodeId): void;
106
+ finish(frameIndex: number): FrameTiming;
107
+ }
108
+
109
+ export function createTimingCollector(): TimingCollector {
110
+ const nodeTimings = new Map<NodeId, { start: number; end: number }>();
111
+
112
+ return {
113
+ nodeTimings,
114
+
115
+ beginNode(nodeId: NodeId): void {
116
+ nodeTimings.set(nodeId, { start: performance.now(), end: 0 });
117
+ },
118
+
119
+ endNode(nodeId: NodeId): void {
120
+ const timing = nodeTimings.get(nodeId);
121
+ if (timing) {
122
+ timing.end = performance.now();
123
+ }
124
+ },
125
+
126
+ finish(frameIndex: number): FrameTiming {
127
+ const nodes: NodeTiming[] = [];
128
+ let totalCpuMs = 0;
129
+
130
+ for (const [nodeId, timing] of nodeTimings) {
131
+ const cpuMs = timing.end - timing.start;
132
+ totalCpuMs += cpuMs;
133
+ nodes.push({ nodeId, cpuMs });
134
+ }
135
+
136
+ return { frameIndex, nodes, totalCpuMs };
137
+ },
138
+ };
139
+ }
@@ -1,7 +1,7 @@
1
1
  import { setTraits } from "../../core/component";
2
2
  import { WorldTransform } from "../transforms";
3
3
  import { clearColor } from "./forward";
4
- import { perspective, multiply, invert } from "./scene";
4
+ import { perspective, orthographic, multiply, invert } from "./scene";
5
5
 
6
6
  export const RenderMode = {
7
7
  Raster: 0,
@@ -16,6 +16,11 @@ export const DebugMode = {
16
16
  Hit: 4,
17
17
  } as const;
18
18
 
19
+ export const CameraMode = {
20
+ Perspective: 0,
21
+ Orthographic: 1,
22
+ } as const;
23
+
19
24
  export const Camera = {
20
25
  fov: [] as number[],
21
26
  near: [] as number[],
@@ -24,6 +29,8 @@ export const Camera = {
24
29
  clearColor: [] as number[],
25
30
  renderMode: [] as number[],
26
31
  debugMode: [] as number[],
32
+ mode: [] as number[],
33
+ size: [] as number[],
27
34
  };
28
35
 
29
36
  setTraits(Camera, {
@@ -35,6 +42,8 @@ setTraits(Camera, {
35
42
  clearColor: 0x1a1a1a,
36
43
  renderMode: RenderMode.Raster,
37
44
  debugMode: DebugMode.Color,
45
+ mode: CameraMode.Perspective,
46
+ size: 5,
38
47
  }),
39
48
  });
40
49
 
@@ -77,7 +86,10 @@ export function uploadCamera(
77
86
  clearColor.g = color.g;
78
87
  clearColor.b = color.b;
79
88
 
80
- const proj = perspective(Camera.fov[eid], aspect, Camera.near[eid], Camera.far[eid]);
89
+ const proj =
90
+ Camera.mode[eid] === CameraMode.Orthographic
91
+ ? orthographic(Camera.size[eid], aspect, Camera.near[eid], Camera.far[eid])
92
+ : perspective(Camera.fov[eid], aspect, Camera.near[eid], Camera.far[eid]);
81
93
  const world = WorldTransform.data.subarray(eid * 16, eid * 16 + 16);
82
94
  const view = invert(world);
83
95
  const viewProj = multiply(proj, view);
@@ -66,6 +66,35 @@ export function perspective(fov: number, aspect: number, near: number, far: numb
66
66
  ]);
67
67
  }
68
68
 
69
+ export function orthographic(
70
+ size: number,
71
+ aspect: number,
72
+ near: number,
73
+ far: number
74
+ ): Float32Array {
75
+ const lr = 1 / (size * aspect);
76
+ const bt = 1 / size;
77
+ const nf = 1 / (near - far);
78
+ return new Float32Array([
79
+ lr,
80
+ 0,
81
+ 0,
82
+ 0,
83
+ 0,
84
+ bt,
85
+ 0,
86
+ 0,
87
+ 0,
88
+ 0,
89
+ nf,
90
+ 0,
91
+ 0,
92
+ 0,
93
+ near * nf,
94
+ 1,
95
+ ]);
96
+ }
97
+
69
98
  export function multiply(a: Float32Array, b: Float32Array): Float32Array {
70
99
  const out = new Float32Array(16);
71
100
  for (let i = 0; i < 4; i++) {
@@ -8,6 +8,6 @@ export {
8
8
  type TweenOptions,
9
9
  } from "./tween";
10
10
 
11
- export { Sequence, SequenceState, Pause, computeTweenDelays, finalizeSequences } from "./sequence";
11
+ export { Sequence, SequenceState, Pause, finalizeSequences } from "./sequence";
12
12
 
13
13
  export { EASING_FUNCTIONS, getEasing, getEasingIndex, type EasingFn } from "./easing";
@@ -64,6 +64,10 @@ function updateSequencePlayheads(state: State, dt: number): void {
64
64
  if (Sequence.state[seqEid] !== SequenceState.PLAYING) continue;
65
65
 
66
66
  const prevElapsed = Sequence.elapsed[seqEid] ?? 0;
67
+
68
+ if (prevElapsed === 0) {
69
+ computeTweenDelays(state, seqEid);
70
+ }
67
71
  const elapsed = prevElapsed + dt;
68
72
  Sequence.elapsed[seqEid] = elapsed;
69
73
 
@@ -135,7 +135,6 @@ export function finalizePendingTweens(state: State, context: ParseContext): void
135
135
  if (!binding) continue;
136
136
 
137
137
  state.addRelation(pending.tweenEid, TweenTarget, targetEid);
138
-
139
138
  Tween.to[pending.tweenEid] = parseFloat(pending.to);
140
139
  }
141
140
  pendingXmlTweens = [];