kireji 0.6.8 → 0.7.3

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": "kireji",
3
- "version": "0.6.8",
3
+ "version": "0.7.3",
4
4
  "description": "A web framework for stateful, entropy-perfect, multi-origin web applications. Currently in alpha. Expect breaking changes for version 0. Use with caution!",
5
5
  "files": [
6
6
  "src/",
@@ -163,7 +163,6 @@ const pointerConfig = {
163
163
  tabGroup.recomputeRouteID(true)
164
164
  } else {
165
165
 
166
- debug("B", tabGroup.previewTabIndex === null, BigInt(tabGroup.openTabs.length), tabGroup.maxTabCount)
167
166
  if (BigInt(tabGroup.openTabs.length) === tabGroup.maxTabCount) {
168
167
  alert("You have too many tabs open!")
169
168
  return
package/src/build.js CHANGED
@@ -90,6 +90,62 @@ function ƒ(_, compressedSubjectOrigins) {
90
90
  result[dimension] = vector[dimension] / magnitude
91
91
  return result
92
92
  }
93
+ static sign(vector) {
94
+ const result = {}
95
+ for (const dimension in vector)
96
+ result[dimension] = Math.sign(vector[dimension])
97
+ return result
98
+ }
99
+ static floor(vector) {
100
+ const result = {}
101
+ for (const dimension in vector)
102
+ result[dimension] = Math.floor(vector[dimension])
103
+ return result
104
+ }
105
+ static round(vector) {
106
+ const result = {}
107
+ for (const dimension in vector)
108
+ result[dimension] = Math.round(vector[dimension])
109
+ return result
110
+ }
111
+ static operate(value1, value2, operation) {
112
+ if (typeof value1 === "number" || typeof value2 === "number") {
113
+ if (typeof value1 === "number" && typeof value2 === "number")
114
+ return operation(value1, value2)
115
+
116
+ const result = {}
117
+ const number = typeof value1 === "number" ? value1 : value2
118
+ const vector = typeof value1 === "object" ? value1 : value2
119
+
120
+ for (const dimension in vector)
121
+ result[dimension] = operation(vector[dimension], number)
122
+
123
+ return result
124
+ }
125
+
126
+ const result = {}
127
+ const dimensions = Object.keys(value1)
128
+
129
+ if (dimensions.some(key => !(key in value2)) || Object.keys(value2).some(key => !(key in value1)))
130
+ throw new Error(`Vector Operation Error: the two vectors do not have the same keys.`)
131
+
132
+ for (const dimension of dimensions)
133
+ result[dimension] = operation(value1[dimension], value2[dimension])
134
+
135
+ return result
136
+ }
137
+ static add(value1, value2) {
138
+ return this.operate(value1, value2, (a, b) => a + b)
139
+ }
140
+ static subtract(value1, value2) {
141
+ return this.operate(value1, value2, (a, b) => a - b)
142
+ }
143
+ static multiply(value1, value2) {
144
+ return this.operate(value1, value2, (a, b) => a * b)
145
+ }
146
+ static dot(vector1, vector2) {
147
+ return vector1.x * vector2.x + vector1.y * vector2.y
148
+ }
93
149
  }
94
150
  class FenwickTree {
95
151
  static LSB = []
@@ -0,0 +1,84 @@
1
+ mesh.define({
2
+ triTable: { value: [] },
3
+ triIndex: { value: -1, writable: true },
4
+ position: { value: { x: null, y: null } },
5
+ cardinality: {
6
+ resolve() {
7
+ let meshCardinality = 0n
8
+
9
+ // Obtain the raw data for this mesh.
10
+ const [pointList, tris] = mesh.getData()
11
+
12
+ // Iterate over each tri (array of three point indices) in the data.
13
+ for (const tri of tris) {
14
+
15
+ // Prepare the per-tri cardinality sum.
16
+ let triCardinality = 0n
17
+
18
+ // Recover the true points of the tri.
19
+ const points = tri.map(pointID => pointList[pointID])
20
+
21
+ // Get just the y-coordinate of all the points.
22
+ const yPoints = points.map(point => point[1])
23
+
24
+ // Estimate the y-range of this tri.
25
+ const range = {
26
+ min: Math.min(...yPoints),
27
+ max: Math.max(...yPoints)
28
+ }
29
+
30
+ // Iterate over the range inclusively to populate each row subspace.
31
+ const rows = []
32
+ for (let rowID = range.min; rowID <= range.max; rowID++) {
33
+
34
+ // This irrational offset ensures that all grid points lie vertically in at most one triangular region.
35
+ const offsetRowID = rowID + (Math.PI / 3.141 - 0.5)
36
+
37
+ // Iterate over the 3 lines in order to determine where the edges intersect the given row.
38
+ const intersections = []
39
+ for (let edgeID = 0; edgeID < 3; edgeID++) {
40
+
41
+ const pointA = points[edgeID]
42
+ const pointB = points[(edgeID + 1) % 3]
43
+
44
+ if ((pointA[1] <= offsetRowID && pointB[1] > offsetRowID) || (pointB[1] <= offsetRowID && pointA[1] > offsetRowID))
45
+ intersections.push(pointA[0] + ((offsetRowID - pointA[1]) / (pointB[1] - pointA[1])) * (pointB[0] - pointA[0]))
46
+ }
47
+
48
+ if (intersections.length < 2) {
49
+ // This row doesn't have a full pixel. Adjust the range.
50
+ if (rowID <= range.min + 1) range.min = rowID + 1
51
+ else if (rowID >= range.max - 1) range.max = rowID - 1
52
+ else throw new Error("Unexpected tri geometry found in mesh.")
53
+ continue
54
+ }
55
+
56
+ /** @type {IMeshTriDataRow} */
57
+ const row = {
58
+ y: rowID,
59
+ range: {
60
+ min: Math.ceil(Math.min(...intersections)),
61
+ max: Math.ceil(Math.max(...intersections)) - 1,
62
+ },
63
+ offset: triCardinality
64
+ }
65
+ rows.push(row)
66
+ triCardinality += BigInt(row.range.max - row.range.min + 1)
67
+ }
68
+
69
+ // Store the tri.
70
+ mesh.triTable.push({
71
+ points,
72
+ range,
73
+ rows,
74
+ offset: meshCardinality,
75
+ cardinality: triCardinality
76
+ })
77
+
78
+ meshCardinality += triCardinality
79
+ }
80
+
81
+ return meshCardinality
82
+ }
83
+ }
84
+ })
@@ -0,0 +1 @@
1
+ const mesh = this
@@ -0,0 +1 @@
1
+ return { ...mesh.position }
@@ -0,0 +1,18 @@
1
+ {
2
+ "abstract": true,
3
+ "methods": {
4
+ "data-get": [],
5
+ "point-tri-contains": [
6
+ "TRI_INDEX",
7
+ "POINT"
8
+ ],
9
+ "point-tri-that-contains": [
10
+ "POINT"
11
+ ],
12
+ "ray-cast": [
13
+ "FORCE_VECTOR",
14
+ "DELTA_TIME",
15
+ "ENABLE_SLIDING"
16
+ ]
17
+ }
18
+ }
@@ -0,0 +1,14 @@
1
+ const roundedY = Math.floor(POINT.y)
2
+ const triData = mesh.triTable[TRI_INDEX]
3
+
4
+ if (roundedY < triData.range.min || roundedY > triData.range.max)
5
+ return false
6
+
7
+ const row = triData.rows[roundedY - triData.range.min]
8
+
9
+ if (!row)
10
+ return false
11
+
12
+ const roundedX = Math.floor(POINT.x)
13
+
14
+ return roundedX >= row.range.min && roundedX <= row.range.max
@@ -0,0 +1,10 @@
1
+ // Check current tri first.
2
+ if (mesh.triIndex !== -1 && mesh.triContainsPoint(mesh.triIndex, POINT))
3
+ return mesh.triIndex
4
+
5
+ // Check all tris. TODO: Memoize neighbors.
6
+ for (let triIndex = 0; triIndex < mesh.triTable.length; triIndex++)
7
+ if (triIndex !== mesh.triIndex && mesh.triContainsPoint(triIndex, POINT))
8
+ return triIndex
9
+
10
+ return -1
@@ -0,0 +1,262 @@
1
+ // Define a safe result: the current position before casting.
2
+ const safeIterationResult = {
3
+ hit: false,
4
+ triIndex: mesh.triIndex,
5
+ point: { ...mesh.position },
6
+ forceVector: FORCE_VECTOR
7
+ }
8
+
9
+ // Obtain the speed of the force vector, which will be used later if we need to slide along the boundary.
10
+ const speed = Vector.magnitude(FORCE_VECTOR)
11
+
12
+ // If there's no motion, nothing will happen; return the safe result.
13
+ if (speed === 0)
14
+ return safeIterationResult
15
+
16
+ // Otherwise, initialize timing data as though the vector doesn't intersect any grid lines.
17
+ const timeOfNextIntersection = { x: Infinity, y: Infinity }
18
+ const timeBetweenIntersections = { ...timeOfNextIntersection }
19
+
20
+ // Set the clock to zero.
21
+ let time = 0
22
+
23
+ function initalizeRayIntersectionSchedule(alsoComputeIntersectionInterval) {
24
+
25
+ // If the force vector isn't parallel to the x axis...
26
+ if (FORCE_VECTOR.x !== 0) {
27
+
28
+ // Determine the x coordinate of the first grid line the ray may intersect.
29
+ const nextGridX = Math.floor(safeIterationResult.point.x) + (FORCE_VECTOR.x > 0)
30
+
31
+ // Compute the exact moment that intersection will happen.
32
+ timeOfNextIntersection.x = time + (nextGridX - safeIterationResult.point.x) / FORCE_VECTOR.x
33
+
34
+ // Compute the constant time between each grid line intersection.
35
+ if (alsoComputeIntersectionInterval)
36
+ timeBetweenIntersections.x = 1 / Math.abs(FORCE_VECTOR.x)
37
+ }
38
+
39
+ // Perform the same steps for the y-axis.
40
+ if (FORCE_VECTOR.y !== 0) {
41
+ const nextGridY = Math.floor(safeIterationResult.point.y) + (FORCE_VECTOR.y > 0)
42
+ timeOfNextIntersection.y = time + (nextGridY - safeIterationResult.point.y) / FORCE_VECTOR.y
43
+ if (alsoComputeIntersectionInterval)
44
+ timeBetweenIntersections.y = 1 / Math.abs(FORCE_VECTOR.y)
45
+ }
46
+ }
47
+
48
+ initalizeRayIntersectionSchedule(true)
49
+
50
+ const start = _.now
51
+
52
+ let iteration = 0
53
+
54
+ // Start iterating on the ray cast.
55
+ while (true) {
56
+
57
+ iteration++
58
+
59
+ // Emergency exit the loop.
60
+ if (_.now - start >= (DELTA_TIME * 1000))
61
+ throw { error: "LOCKED WHILE LOOP", processingTime: _.now - start, iteration, DELTA_TIME, FORCE_VECTOR, speed, safeIterationResult, timeOfNextIntersection, timeBetweenIntersections }
62
+
63
+ // Pick the dimension whose grid intersection will happen next.
64
+ const dimension = timeOfNextIntersection.x < timeOfNextIntersection.y ? "x" : "y"
65
+
66
+ // Note how much time must elapse to reach that intersection.
67
+ let timeElapsedDuringThisIteration = timeOfNextIntersection[dimension] - time
68
+
69
+ // Set the clock to the moment of that intersection.
70
+ time += timeElapsedDuringThisIteration
71
+
72
+ // Note if this is a case where the grid intersection is reached after the time limit.
73
+ let ranOutOfTime = time >= DELTA_TIME
74
+
75
+ // In that case...
76
+ if (ranOutOfTime) {
77
+
78
+ // Measure how far we are past the time limit.
79
+ const overshootTime = time - DELTA_TIME
80
+
81
+ // Adjust how much time we say elapsed.
82
+ timeElapsedDuringThisIteration -= overshootTime
83
+
84
+ // Set the block back to the last possible moment.
85
+ time = DELTA_TIME
86
+ }
87
+
88
+ // Construct the next point along the ray, given the elapsed time.
89
+ const point = Vector.add(safeIterationResult.point, Vector.multiply(FORCE_VECTOR, timeElapsedDuringThisIteration))
90
+
91
+ // Check if the point is outside the mesh boundary.
92
+ const triIndex = mesh.triThatContainsPoint(point)
93
+
94
+ // If it is...
95
+ if (triIndex === -1) {
96
+
97
+ // We are at a boundary pixel. Let's move our position to exactly the center of our current point.
98
+ // safeIterationResult.point = Vector.add(Vector.floor(safeIterationResult.point), 0.5)
99
+
100
+ // If we have permission to simulate sliding...
101
+ if (ENABLE_SLIDING) {
102
+
103
+ // Walk back the clock to before the ray hit.
104
+ time -= timeElapsedDuringThisIteration
105
+
106
+ const neighbor = {
107
+ dot: -Infinity,
108
+ point: null,
109
+ triIndex: null,
110
+ direction: {
111
+ x: null,
112
+ y: null
113
+ }
114
+ }
115
+
116
+ let boundaryAppearsFlat = false
117
+
118
+ // Search the local neighborhood of points for one that we can safely move to.
119
+ for (let x = -1; x <= 1; x++) for (let y = -1; y <= 1; y++) {
120
+
121
+ // Exclude the point itself
122
+ if (!x && !y)
123
+ continue
124
+
125
+ const direction = { x, y }
126
+
127
+ // Find out how much of the force vector is cancelled out by going to this neighbor.
128
+ const dot = Vector.dot(Vector.normalize(direction), Vector.normalize(FORCE_VECTOR))
129
+
130
+ // If it would require traveling "against" or perpendicular to the force vector, exclude it from consideration.
131
+ if (dot < 0)
132
+ continue
133
+
134
+ // Get the position of the center of the neighbor.
135
+ const point = Vector.add(Vector.floor(Vector.add(safeIterationResult.point, direction)), 0.5)
136
+ const triIndex = mesh.triThatContainsPoint(point)
137
+
138
+ // If it isn't part of the mesh, exclude it from consideration.
139
+ if (triIndex === -1)
140
+ continue
141
+
142
+ // Store the neighbor which most agrees with the force vector.
143
+ if (dot >= neighbor.dot) {
144
+ if (dot < 0.05 && neighbor.dot !== -Infinity && neighbor.dot < 0.05) {
145
+ boundaryAppearsFlat = true
146
+ // Boundary is "flat". Don't allow perpendicular motion.
147
+ neighbor.dot = -Infinity
148
+ neighbor.point = null
149
+ neighbor.triIndex = null
150
+ neighbor.direction.x = null
151
+ neighbor.direction.y = null
152
+ } else {
153
+ boundaryAppearsFlat = false
154
+ neighbor.dot = dot
155
+ neighbor.point = point
156
+ neighbor.triIndex = triIndex
157
+ neighbor.direction = direction
158
+ }
159
+ }
160
+ }
161
+
162
+ if (boundaryAppearsFlat) {
163
+ // TODO: Consider using the line through the opposing neighbors to search for assymetry in the mesh boundry.
164
+ }
165
+
166
+ // If there is no neighboring point, we are "stuck". The ray cast ends here.
167
+ if (!neighbor.point) {
168
+
169
+ // Return the safe result data.
170
+ safeIterationResult.hit = true
171
+ return safeIterationResult
172
+ }
173
+
174
+ // Compute travel distance to the center of the neighbor.
175
+ const vectorToNeighbor = Vector.subtract(neighbor.point, safeIterationResult.point)
176
+ const distance = Vector.magnitude(vectorToNeighbor)
177
+
178
+ // Construct a force vector pointing to the chosen neighbor.
179
+ // TODO: speed should actually be reduced.
180
+ const forceVectorToNeighbor = Vector.multiply(Vector.normalize(vectorToNeighbor), speed)
181
+
182
+ // Compute travel time to reach the neighbor.
183
+ timeElapsedDuringThisIteration = distance / speed
184
+
185
+ // Move the clock ahead to our arrival at the neighbor.
186
+ time += timeElapsedDuringThisIteration
187
+
188
+ // Check again if we ran out of time.
189
+ ranOutOfTime = time >= DELTA_TIME
190
+ if (ranOutOfTime) {
191
+ const overshootTime = time - DELTA_TIME
192
+ timeElapsedDuringThisIteration -= overshootTime
193
+ time = DELTA_TIME
194
+
195
+ // Construct the point along the way to the neighbor.
196
+ const point = Vector.add(safeIterationResult.point, Vector.multiply(forceVectorToNeighbor, timeElapsedDuringThisIteration))
197
+ const triIndex = mesh.triThatContainsPoint(point)
198
+ safeIterationResult.forceVector = forceVectorToNeighbor
199
+
200
+ if (triIndex === -1) {
201
+ // We didn't have enough time to overcome the diagonal boundary.
202
+
203
+ if (speed * DELTA_TIME > Math.SQRT2) {
204
+ // The current speed is enough to overcome diagonal corners.
205
+
206
+ // Return the previous safe point, chalking the lost time up to friction.
207
+ return safeIterationResult
208
+ }
209
+
210
+ // There is not enough velocity to overcome the diagonal. Use random chance.
211
+ if (Math.random() < Math.SQRT1_2) {
212
+ safeIterationResult.point = neighbor.point
213
+ safeIterationResult.triIndex = neighbor.triIndex
214
+ }
215
+
216
+ return safeIterationResult
217
+ } else {
218
+
219
+ // Go to the partial point.
220
+ safeIterationResult.point = point
221
+ safeIterationResult.triIndex = triIndex
222
+ }
223
+
224
+ return safeIterationResult
225
+
226
+ // TODO: Consider moving toward a 4-point neighbor instead.
227
+ }
228
+
229
+ // Set the neighbor as the new safe point.
230
+ safeIterationResult.point = neighbor.point
231
+ safeIterationResult.triIndex = neighbor.triIndex
232
+ safeIterationResult.forceVector = FORCE_VECTOR
233
+
234
+ // The normal ray intersection schedule has changed.
235
+ initalizeRayIntersectionSchedule()
236
+
237
+ // Proceed with normal ray casting behavior from this point.
238
+ continue
239
+
240
+ } else {
241
+
242
+ // Discard the unsafe point and return the safe result data.
243
+ safeIterationResult.hit = true
244
+ return safeIterationResult
245
+ }
246
+ }
247
+
248
+
249
+ // Otherwise, update the safe result.
250
+ safeIterationResult.point = point
251
+ safeIterationResult.triIndex = triIndex
252
+
253
+ // If we ran out of time...
254
+ if (ranOutOfTime) {
255
+
256
+ // We're done. Return the safe point.
257
+ return safeIterationResult
258
+ }
259
+
260
+ // Otherwise, Prepare for the next grid line time of this dimension.
261
+ timeOfNextIntersection[dimension] += timeBetweenIntersections[dimension]
262
+ }
@@ -0,0 +1,42 @@
1
+ mesh.updateRouteID(ROUTE_ID)
2
+
3
+ // Binary search better than embedded match.
4
+ mesh.triIndex = (() => {
5
+
6
+ let low = 0
7
+ let high = mesh.triTable.length - 1
8
+
9
+ while (low <= high) {
10
+
11
+ const mid = (low + high) >>> 1
12
+ const triData = mesh.triTable[mid]
13
+ const nextTriData = mesh.triTable[mid + 1]
14
+
15
+ if (ROUTE_ID < triData.offset)
16
+ high = mid - 1
17
+ else if (nextTriData && ROUTE_ID >= nextTriData.offset)
18
+ low = mid + 1
19
+ else
20
+ return mid
21
+ }
22
+
23
+ // Fallback.
24
+ return 0
25
+ })()
26
+
27
+ ROUTE_ID -= mesh.triTable[mesh.triIndex].offset
28
+
29
+ // Embedded match can become binary search later.
30
+ for (const row of mesh.triTable[mesh.triIndex].rows) {
31
+
32
+ if (!row)
33
+ continue
34
+
35
+ const rowWidth = BigInt(row.range.max - row.range.min + 1)
36
+
37
+ if (ROUTE_ID < row.offset + rowWidth) {
38
+ mesh.position.y = row.y
39
+ mesh.position.x = row.range.min + Number(ROUTE_ID - row.offset)
40
+ break
41
+ }
42
+ }
@@ -0,0 +1,5 @@
1
+ // TODO: Validate MODEL and handle out-of-range points.
2
+ const triIndex = mesh.triThatContainsPoint(MODEL)
3
+ const triData = mesh.triTable[triIndex]
4
+ const row = triData.rows[Math.floor(MODEL.y) - triData.range.min]
5
+ return triData.offset + row.offset + BigInt(Math.floor(MODEL.x) - row.range.min)
@@ -0,0 +1,85 @@
1
+ declare interface IMesh<TOwner>
2
+ extends IPart<TOwner, null> {
3
+
4
+ // Serialized Properties.
5
+ readonly getData(): IMeshData
6
+ /** Casts a ray from the current mesh position along the force vector direction for the given delta time and returns a summary of the results. @param FORCE_VECTOR the force vector represent the position the uninterrupted ray will arrive at in one second. @param DELTA_TIME the duration of the time cast in seconds. @param ENABLE_SLIDING whether or not to enable the ray to "wrap" along the mesh boundary instead of stopping cold. */
7
+ readonly castRay(FORCE_VECTOR: IVector2, DELTA_TIME: number, ENABLE_SLIDING: boolean): {
8
+ /** Whether or not the ray hit the mesh boundary. */
9
+ readonly hit: boolean
10
+ /** The tri the ray most recently occupied when it hit the boundary or the index of the boundary where the ray stopped if there is no hit. */
11
+ readonly triIndex: IMeshTriIndex
12
+ /** The rounded cell position where the cast ray landed. */
13
+ readonly position: IVector2
14
+ /** The force vector used to cast the ray, which might be different from the input if sliding is enabled as it may point along the direction of the most recent sliding iteration. */
15
+ readonly forceVector: IVector2
16
+ }
17
+ /** Checks if a point (x, y) rounds to a valid pixel within the specified tri's memoized row data. */
18
+ readonly triContainsPoint(TRI_INDEX: IMeshTriIndex, POINT: IVector2): boolean
19
+ /** Returns whether or not the given point is in a tri. @returns the index of the tri that contains the point. -1 otherwise. */
20
+ readonly triThatContainsPoint(POINT: IVector2): IMeshTriIndex
21
+
22
+ // Runtime Properties.
23
+ /** The memoization data of every tri in the mesh, stored by tri index at build-time. */
24
+ readonly triTable: IMeshTriData[]
25
+ /** The index of the current tri in the mesh. */
26
+ readonly triIndex?: IMeshTriIndex
27
+ /** The current position of the state in the mesh. */
28
+ readonly position: IVector2
29
+ }
30
+
31
+ declare interface IMeshTriData {
32
+ /** The original point array of the tri as taken from `mesh.getData()`. */
33
+ readonly points: IMeshTriPoints
34
+ /** The y-axis range of the tri. */
35
+ readonly range: {
36
+ /** The y position of the top-most row of the tri. */
37
+ readonly min: number
38
+ /** The y position of the bottom-most row of the tri. */
39
+ readonly max: number
40
+ }
41
+ /** The rows of pixels in the tri. */
42
+ readonly rows: IMeshTriDataRow[]
43
+ /** The offset of the tri in the overall mesh. */
44
+ readonly offset: bigint
45
+ /** The total number of pixels in the tri. */
46
+ readonly cardinality: bigint
47
+ }
48
+
49
+ declare interface IMeshTriDataRow {
50
+ /** The y position of this row of the tri's space. */
51
+ readonly y: number
52
+ /** The x-axis range of the row. */
53
+ readonly range: {
54
+ /** The smallest x position that is within the tri row. */
55
+ readonly min: number
56
+ /** The largest x position that is within the tri row. */
57
+ readonly max: number
58
+ }
59
+ /** The bigint offset of this row in the overall tri. */
60
+ readonly offset: bigint
61
+ }
62
+
63
+ declare type IMeshData = [IMeshPoint[], IMeshTri[]]
64
+
65
+ declare type IMeshTri =
66
+ [IMeshPointIndex, IMeshPointIndex, IMeshPointIndex]
67
+
68
+ declare type IMeshPointIndex =
69
+ number
70
+
71
+ declare type IMeshTriPoints =
72
+ [IMeshPoint, IMeshPoint, IMeshPoint]
73
+
74
+ declare type IMeshPoint =
75
+ [x: number, y: number]
76
+
77
+ declare type IMeshTriIndex =
78
+ number
79
+
80
+ declare type IMeshAny =
81
+ IMesh<IPartAny>
82
+
83
+ declare const mesh: IMeshAny
84
+
85
+ declare const TRI: IMeshTriData
@@ -1,14 +1,14 @@
1
1
  if (part.isAbstract)
2
- throw new Error(`You can't set the route ID of an abstract part. (${part.host})`)
2
+ throw new Error(`Route ID Update Error: You can't set the route ID of an abstract part. (${part.host})`)
3
3
 
4
4
  if (ROUTE_ID === undefined)
5
- throw new Error(`The ROUTE_ID argument is missing. ` + part.host)
5
+ throw new Error(`Route ID Update Error: The ROUTE_ID argument is missing. ` + part.host)
6
6
 
7
7
  if (typeof ROUTE_ID !== "bigint" || ROUTE_ID < -1n)
8
- throw new Error(`Route ID is invalid. ` + part.host)
8
+ throw new Error(`Route ID Update Error: Route ID is invalid. ` + part.host)
9
9
 
10
10
  if (ROUTE_ID >= part.cardinality)
11
- throw new Error(`Route ID (${ROUTE_ID}) out of range (max = ${part.cardinality - 1n}). ` + part.host)
11
+ throw new Error(`Route ID Update Error: Route ID (${ROUTE_ID}) out of range (max = ${part.cardinality - 1n}). ` + part.host)
12
12
 
13
13
  part.previousRouteID = part.routeID
14
14
  part.routeID = ROUTE_ID
@@ -20,6 +20,6 @@ part.justDisabled = !part.enabled && part.wasEnabled
20
20
  part.deltaRouteID = part.routeID - part.previousRouteID
21
21
 
22
22
  if (part.deltaRouteID === 0n)
23
- warn(`Reassigned route ID (${part.routeID}) to part.\n ${part[".."] ? `${part[".."].key} {\n ${part.key} : "${part.host}" // <--- this part \n }` : part.host}`)
23
+ warn(`Route ID Update Warning: Reassigned route ID (${part.routeID}) to part.\n ${part[".."] ? `${part[".."].key} {\n ${part.key} : "${part.host}" // <--- this part \n }` : part.host}`)
24
24
 
25
25
  part.dirty = true
@@ -37,6 +37,7 @@ globalThis.addEventListener("keyup", keyboardEvent => {
37
37
  })
38
38
 
39
39
  globalThis.addEventListener("keydown", keyboardEvent => {
40
+
40
41
  if (!keyboardEvent.repeat) {
41
42
  /* This handles the edge case when the user is holding modifier keys that
42
43
  * they pressed while not focused on this instance of the ecosystem. */
@@ -64,15 +65,24 @@ globalThis.addEventListener("keydown", keyboardEvent => {
64
65
 
65
66
  hotKeys.pressed.add(keyboardEvent.code)
66
67
 
67
- const combo = hotKeys.combo
68
- const methodName = JSON.parse(_.application["hot-keys.json"] ?? "{}")[combo] ?? hotKeys.table[combo]
69
- const method = methodName && (_.application[methodName] ?? hotKeys[methodName])
68
+ const doCombo = () => {
69
+ const combo = hotKeys.combo
70
+ const methodName = JSON.parse(_.application["hot-keys.json"] ?? "{}")[combo] ?? hotKeys.table[combo]
71
+ const method = methodName && (_.application[methodName] ?? hotKeys[methodName])
70
72
 
71
- if (methodName) {
72
- keyboardEvent.preventDefault()
73
- if (typeof method === "function")
74
- method()
75
- // else warn(`Hot Keys Warning: method called ${methodName} is not defined on either the hot keys manager or the current application.`)
73
+ if (methodName) {
74
+ keyboardEvent.preventDefault()
75
+ if (typeof method === "function")
76
+ method()
77
+ // else warn(`Hot Keys Warning: method called ${methodName} is not defined on either the hot keys manager or the current application.`)
78
+ }
76
79
  }
80
+
81
+ // Prevent a bug when the user presses keys too early.
82
+ if (client.hydrated)
83
+ doCombo()
84
+ else
85
+ client.promise.then(doCombo)
86
+
77
87
  }
78
88
  })
@@ -1,2 +1,4 @@
1
- const agent = _.parts.core.agent
2
- const hotKeys = this
1
+ const hotKeys = this
2
+ const core = hotKeys[".."]
3
+ const agent = core.agent
4
+ const client = core.client
package/src/type.d.ts CHANGED
@@ -308,6 +308,18 @@ declare class FenwickTree {
308
308
  declare class Vector {
309
309
  static magnitude(vector: IVector): number
310
310
  static normalize(vector: IVector): IVector
311
+ static sign(vector: IVector): IVector
312
+ static floor(vector: IVector): IVector
313
+ /** Performs the given binary operation on the two values, which can each be either a vector or a number. If at least one of the values is a vector, returns a vector. Otherwise, returns a number. */
314
+ static operate(value1: IVector | number, value2: IVector | number, operation: (a, b) => number): IVector | number
315
+ /** Adds the two values, which can each be either a vector or a number. If at least one of the values is a vector, returns a vector. Otherwise, returns a number. */
316
+ static add(value1: IVector | number, value2: IVector | number): IVector | number
317
+ /** Subtracts the two values, which can each be either a vector or a number. If at least one of the values is a vector, returns a vector. Otherwise, returns a number. */
318
+ static subtract(value1: IVector | number, value2: IVector | number): IVector | number
319
+ /** Multiplies the two values, which can each be either a vector or a number. If at least one of the values is a vector, returns a vector. Otherwise, returns a number. */
320
+ static multiply(value1: IVector | number, value2: IVector | number): IVector | number
321
+ static dot(vector1: IVector, vector2: IVector): number
311
322
  }
312
323
 
313
- declare type IVector = Record<string, number>
324
+ declare type IVector = Record<string, number>
325
+ declare type IVector2 = { x: number, y: number }