@jgengine/core 0.6.0 → 0.8.0
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/CHANGELOG.md +214 -2
- package/README.md +2 -0
- package/dist/ai/crowd.d.ts +36 -0
- package/dist/ai/crowd.js +213 -0
- package/dist/ai/jobBoard.d.ts +49 -0
- package/dist/ai/jobBoard.js +155 -0
- package/dist/ai/spawnDirector.d.ts +61 -0
- package/dist/ai/spawnDirector.js +162 -0
- package/dist/ai/threat.d.ts +25 -0
- package/dist/ai/threat.js +67 -0
- package/dist/anim/easing.d.ts +14 -0
- package/dist/anim/easing.js +55 -0
- package/dist/audio/audioFalloff.d.ts +38 -0
- package/dist/audio/audioFalloff.js +32 -0
- package/dist/behaviour/behaviour.d.ts +136 -0
- package/dist/behaviour/behaviour.js +388 -0
- package/dist/board/laneBoard.d.ts +63 -0
- package/dist/board/laneBoard.js +114 -0
- package/dist/board/timelineBoard.d.ts +36 -0
- package/dist/board/timelineBoard.js +70 -0
- package/dist/cards/cardPile.d.ts +64 -0
- package/dist/cards/cardPile.js +175 -0
- package/dist/cards/modifierPipeline.d.ts +33 -0
- package/dist/cards/modifierPipeline.js +48 -0
- package/dist/combat/abilityKit.d.ts +47 -0
- package/dist/combat/abilityKit.js +139 -0
- package/dist/combat/animationState.d.ts +47 -0
- package/dist/combat/animationState.js +101 -0
- package/dist/combat/attackTags.d.ts +14 -0
- package/dist/combat/attackTags.js +26 -0
- package/dist/combat/breakMeters.d.ts +32 -0
- package/dist/combat/breakMeters.js +51 -0
- package/dist/combat/comboString.d.ts +35 -0
- package/dist/combat/comboString.js +59 -0
- package/dist/combat/death.d.ts +8 -0
- package/dist/combat/death.js +5 -2
- package/dist/combat/defensiveWindow.d.ts +34 -0
- package/dist/combat/defensiveWindow.js +66 -0
- package/dist/combat/downed.d.ts +35 -0
- package/dist/combat/downed.js +104 -0
- package/dist/combat/effects.d.ts +12 -1
- package/dist/combat/effects.js +31 -19
- package/dist/combat/hitReaction.d.ts +23 -0
- package/dist/combat/hitReaction.js +20 -0
- package/dist/combat/projectiles.d.ts +42 -5
- package/dist/combat/projectiles.js +98 -12
- package/dist/combat/resistance.d.ts +13 -0
- package/dist/combat/resistance.js +46 -0
- package/dist/combat/telegraph.d.ts +31 -0
- package/dist/combat/telegraph.js +51 -0
- package/dist/commands/commandRegistry.d.ts +1 -1
- package/dist/commands/commandRegistry.js +2 -1
- package/dist/crafting/crop.d.ts +59 -0
- package/dist/crafting/crop.js +181 -0
- package/dist/crafting/production.d.ts +64 -0
- package/dist/crafting/production.js +130 -0
- package/dist/crafting/recipe.d.ts +62 -0
- package/dist/crafting/recipe.js +101 -0
- package/dist/data/dataSource.d.ts +26 -0
- package/dist/data/dataSource.js +93 -0
- package/dist/data/devProxy.d.ts +11 -0
- package/dist/data/devProxy.js +38 -0
- package/dist/data/fetchJson.d.ts +20 -0
- package/dist/data/fetchJson.js +43 -0
- package/dist/data/jsonDataSource.d.ts +4 -0
- package/dist/data/jsonDataSource.js +6 -0
- package/dist/devtools/devtools.d.ts +157 -0
- package/dist/devtools/devtools.js +484 -0
- package/dist/devtools/transformTunables.d.ts +14 -0
- package/dist/devtools/transformTunables.js +38 -0
- package/dist/economy/sharedWallet.d.ts +28 -0
- package/dist/economy/sharedWallet.js +62 -0
- package/dist/economy/techTree.d.ts +40 -0
- package/dist/economy/techTree.js +66 -0
- package/dist/game/chat.d.ts +66 -0
- package/dist/game/chat.js +165 -0
- package/dist/game/cosmetics.d.ts +25 -0
- package/dist/game/cosmetics.js +51 -0
- package/dist/game/defineGame.d.ts +10 -2
- package/dist/game/defineGame.js +2 -1
- package/dist/game/events.d.ts +87 -0
- package/dist/game/levelSequence.d.ts +44 -0
- package/dist/game/levelSequence.js +78 -0
- package/dist/game/lootFilter.d.ts +33 -0
- package/dist/game/lootFilter.js +48 -0
- package/dist/game/ping.d.ts +67 -0
- package/dist/game/ping.js +86 -0
- package/dist/game/playableGame.d.ts +516 -8
- package/dist/game/playableGame.js +6 -1
- package/dist/game/progression.d.ts +1 -0
- package/dist/game/progression.js +23 -2
- package/dist/game/race.d.ts +100 -0
- package/dist/game/race.js +236 -0
- package/dist/game/runDraft.d.ts +34 -0
- package/dist/game/runDraft.js +97 -0
- package/dist/game/social.d.ts +69 -0
- package/dist/game/social.js +138 -0
- package/dist/game/spawnPoints.d.ts +19 -0
- package/dist/game/spawnPoints.js +23 -0
- package/dist/game/worldItem.d.ts +95 -0
- package/dist/game/worldItem.js +138 -0
- package/dist/input/actionBindings.d.ts +10 -0
- package/dist/input/actionBindings.js +14 -0
- package/dist/input/axisInput.d.ts +43 -0
- package/dist/input/axisInput.js +70 -0
- package/dist/input/gestureSurface.d.ts +31 -0
- package/dist/input/gestureSurface.js +120 -0
- package/dist/input/pointer.d.ts +69 -0
- package/dist/input/pointer.js +65 -0
- package/dist/input/touchScheme.d.ts +83 -0
- package/dist/input/touchScheme.js +115 -0
- package/dist/interaction/contextMenu.d.ts +30 -0
- package/dist/interaction/contextMenu.js +22 -0
- package/dist/interaction/qte.d.ts +20 -0
- package/dist/interaction/qte.js +24 -0
- package/dist/interaction/skillCheck.d.ts +20 -0
- package/dist/interaction/skillCheck.js +26 -0
- package/dist/inventory/shapedGrid.d.ts +56 -0
- package/dist/inventory/shapedGrid.js +176 -0
- package/dist/inventory/storageTier.d.ts +52 -0
- package/dist/inventory/storageTier.js +77 -0
- package/dist/item/affix.d.ts +54 -0
- package/dist/item/affix.js +115 -0
- package/dist/item/durability.d.ts +51 -0
- package/dist/item/durability.js +78 -0
- package/dist/item/modularItem.d.ts +46 -0
- package/dist/item/modularItem.js +75 -0
- package/dist/meta/changelog.d.ts +1 -1
- package/dist/meta/changelog.js +71 -1
- package/dist/movement/dash.d.ts +37 -0
- package/dist/movement/dash.js +65 -0
- package/dist/movement/movementModel.d.ts +36 -3
- package/dist/movement/movementModel.js +81 -5
- package/dist/movement/voxelController.d.ts +61 -0
- package/dist/movement/voxelController.js +229 -0
- package/dist/multiplayer/chatContract.d.ts +42 -0
- package/dist/multiplayer/chatContract.js +57 -0
- package/dist/multiplayer/combatSnapshot.d.ts +45 -0
- package/dist/multiplayer/combatSnapshot.js +89 -0
- package/dist/multiplayer/identity.d.ts +13 -0
- package/dist/multiplayer/identity.js +20 -0
- package/dist/multiplayer/lagCompensation.d.ts +43 -0
- package/dist/multiplayer/lagCompensation.js +154 -0
- package/dist/multiplayer/matchmaking.d.ts +33 -0
- package/dist/multiplayer/matchmaking.js +60 -0
- package/dist/multiplayer/poseSyncGate.d.ts +1 -0
- package/dist/multiplayer/poseSyncGate.js +13 -1
- package/dist/multiplayer/presenceModel.d.ts +3 -0
- package/dist/multiplayer/presenceModel.js +15 -1
- package/dist/multiplayer/simultaneousCommit.d.ts +34 -0
- package/dist/multiplayer/simultaneousCommit.js +53 -0
- package/dist/multiplayer/voiceContract.d.ts +44 -0
- package/dist/multiplayer/voiceContract.js +108 -0
- package/dist/nav/navConstrain.d.ts +14 -0
- package/dist/nav/navConstrain.js +26 -0
- package/dist/nav/navFromEnvironment.d.ts +11 -0
- package/dist/nav/navFromEnvironment.js +16 -0
- package/dist/nav/navGrid.d.ts +56 -0
- package/dist/nav/navGrid.js +265 -0
- package/dist/nav/pathFollow.d.ts +31 -0
- package/dist/nav/pathFollow.js +69 -0
- package/dist/physics/ballisticSweep.d.ts +18 -0
- package/dist/physics/ballisticSweep.js +49 -0
- package/dist/physics/buoyancy.d.ts +49 -0
- package/dist/physics/buoyancy.js +112 -0
- package/dist/physics/carryable.d.ts +46 -0
- package/dist/physics/carryable.js +110 -0
- package/dist/physics/damageZones.d.ts +56 -0
- package/dist/physics/damageZones.js +107 -0
- package/dist/physics/forceVolume.d.ts +48 -0
- package/dist/physics/forceVolume.js +119 -0
- package/dist/physics/physicsWorld.d.ts +124 -2
- package/dist/physics/physicsWorld.js +493 -41
- package/dist/physics/ragdoll.d.ts +49 -0
- package/dist/physics/ragdoll.js +103 -0
- package/dist/physics/spatialGrid.d.ts +41 -0
- package/dist/physics/spatialGrid.js +143 -0
- package/dist/physics/structure.d.ts +83 -0
- package/dist/physics/structure.js +0 -0
- package/dist/physics/traversal.d.ts +75 -0
- package/dist/physics/traversal.js +138 -0
- package/dist/physics/vehicleBody.d.ts +99 -0
- package/dist/physics/vehicleBody.js +233 -0
- package/dist/puzzle/cellGrid.d.ts +26 -0
- package/dist/puzzle/cellGrid.js +110 -0
- package/dist/puzzle/fallingPiece.d.ts +24 -0
- package/dist/puzzle/fallingPiece.js +51 -0
- package/dist/random/nameGen.d.ts +18 -0
- package/dist/random/nameGen.js +35 -0
- package/dist/random/rng.d.ts +2 -0
- package/dist/random/rng.js +23 -0
- package/dist/runtime/adapter.d.ts +35 -0
- package/dist/runtime/adapter.js +37 -1
- package/dist/runtime/cameraDirector.d.ts +10 -0
- package/dist/runtime/cameraDirector.js +25 -0
- package/dist/runtime/gameContext.d.ts +107 -3
- package/dist/runtime/gameContext.js +253 -7
- package/dist/runtime/hostPersistence.d.ts +39 -0
- package/dist/runtime/hostPersistence.js +54 -0
- package/dist/runtime/inputSnapshot.d.ts +7 -0
- package/dist/runtime/inputSnapshot.js +12 -0
- package/dist/runtime/motionIntents.d.ts +25 -0
- package/dist/runtime/motionIntents.js +25 -0
- package/dist/runtime/persistenceScope.d.ts +32 -0
- package/dist/runtime/persistenceScope.js +64 -0
- package/dist/runtime/transport.d.ts +32 -0
- package/dist/scene/autoTarget.d.ts +15 -0
- package/dist/scene/autoTarget.js +54 -0
- package/dist/scene/behaviors.d.ts +13 -1
- package/dist/scene/behaviors.js +3 -0
- package/dist/scene/captureCheck.d.ts +7 -0
- package/dist/scene/captureCheck.js +11 -0
- package/dist/scene/entityStore.d.ts +9 -1
- package/dist/scene/entityStore.js +25 -2
- package/dist/scene/form.d.ts +40 -0
- package/dist/scene/form.js +56 -0
- package/dist/scene/mount.d.ts +62 -0
- package/dist/scene/mount.js +107 -0
- package/dist/scene/movementSpeed.d.ts +35 -0
- package/dist/scene/movementSpeed.js +20 -0
- package/dist/scene/objectQuery.d.ts +18 -0
- package/dist/scene/objectQuery.js +108 -0
- package/dist/scene/objectStore.d.ts +11 -0
- package/dist/scene/objectStore.js +109 -4
- package/dist/scene/paintLayer.d.ts +15 -0
- package/dist/scene/paintLayer.js +53 -0
- package/dist/scene/possession.d.ts +30 -0
- package/dist/scene/possession.js +51 -0
- package/dist/scene/roster.d.ts +25 -0
- package/dist/scene/roster.js +57 -0
- package/dist/scene/selection.d.ts +30 -0
- package/dist/scene/selection.js +38 -0
- package/dist/scene/spatial.d.ts +21 -0
- package/dist/scene/spatial.js +63 -1
- package/dist/scene/stationClaim.d.ts +43 -0
- package/dist/scene/stationClaim.js +109 -0
- package/dist/sensor/concealment.d.ts +21 -0
- package/dist/sensor/concealment.js +63 -0
- package/dist/sensor/freezeMonitor.d.ts +17 -0
- package/dist/sensor/freezeMonitor.js +35 -0
- package/dist/sensor/frustumSensor.d.ts +60 -0
- package/dist/sensor/frustumSensor.js +104 -0
- package/dist/sensor/hiddenStateProbe.d.ts +31 -0
- package/dist/sensor/hiddenStateProbe.js +40 -0
- package/dist/sensor/recordingBuffer.d.ts +29 -0
- package/dist/sensor/recordingBuffer.js +61 -0
- package/dist/sensor/revealQuery.d.ts +22 -0
- package/dist/sensor/revealQuery.js +36 -0
- package/dist/session/contestedChannel.d.ts +36 -0
- package/dist/session/contestedChannel.js +100 -0
- package/dist/session/extraction.d.ts +54 -0
- package/dist/session/extraction.js +108 -0
- package/dist/session/ring.d.ts +36 -0
- package/dist/session/ring.js +88 -0
- package/dist/session/roles.d.ts +6 -0
- package/dist/session/roles.js +32 -0
- package/dist/session/roundState.d.ts +79 -0
- package/dist/session/roundState.js +158 -0
- package/dist/stats/accumulatorMeter.d.ts +31 -0
- package/dist/stats/accumulatorMeter.js +81 -0
- package/dist/stats/eventMeter.d.ts +34 -0
- package/dist/stats/eventMeter.js +76 -0
- package/dist/stats/rollCheck.d.ts +15 -0
- package/dist/stats/rollCheck.js +15 -0
- package/dist/survival/decayMeter.d.ts +59 -0
- package/dist/survival/decayMeter.js +95 -0
- package/dist/survival/moodle.d.ts +48 -0
- package/dist/survival/moodle.js +91 -0
- package/dist/survival/regionHealth.d.ts +72 -0
- package/dist/survival/regionHealth.js +129 -0
- package/dist/tactics/fallingGrid.d.ts +62 -0
- package/dist/tactics/fallingGrid.js +158 -0
- package/dist/tactics/predictiveQuery.d.ts +34 -0
- package/dist/tactics/predictiveQuery.js +44 -0
- package/dist/tactics/snapshot.d.ts +18 -0
- package/dist/tactics/snapshot.js +65 -0
- package/dist/tactics/surface.d.ts +52 -0
- package/dist/tactics/surface.js +135 -0
- package/dist/tactics/tacticalGrid.d.ts +68 -0
- package/dist/tactics/tacticalGrid.js +261 -0
- package/dist/time/beatClock.d.ts +40 -0
- package/dist/time/beatClock.js +73 -0
- package/dist/time/simClock.d.ts +25 -1
- package/dist/time/simClock.js +28 -3
- package/dist/turn/commit.d.ts +27 -0
- package/dist/turn/commit.js +72 -0
- package/dist/turn/intent.d.ts +14 -0
- package/dist/turn/intent.js +29 -0
- package/dist/turn/turnLoop.d.ts +60 -0
- package/dist/turn/turnLoop.js +223 -0
- package/dist/world/buildPermissions.d.ts +47 -0
- package/dist/world/buildPermissions.js +134 -0
- package/dist/world/carve.d.ts +105 -0
- package/dist/world/carve.js +175 -0
- package/dist/world/connectors.d.ts +40 -0
- package/dist/world/connectors.js +67 -0
- package/dist/world/envField.d.ts +71 -0
- package/dist/world/envField.js +96 -0
- package/dist/world/environmentSummary.d.ts +53 -0
- package/dist/world/environmentSummary.js +129 -0
- package/dist/world/features.d.ts +87 -6
- package/dist/world/features.js +52 -2
- package/dist/world/fog.d.ts +40 -0
- package/dist/world/fog.js +137 -0
- package/dist/world/gridInstances.d.ts +13 -0
- package/dist/world/gridInstances.js +22 -0
- package/dist/world/markers.d.ts +52 -0
- package/dist/world/markers.js +96 -0
- package/dist/world/minimap.d.ts +42 -0
- package/dist/world/minimap.js +72 -0
- package/dist/world/placedStructureStore.d.ts +39 -0
- package/dist/world/placedStructureStore.js +95 -0
- package/dist/world/placementController.d.ts +69 -0
- package/dist/world/placementController.js +153 -0
- package/dist/world/realm.d.ts +61 -0
- package/dist/world/realm.js +66 -0
- package/dist/world/support.d.ts +27 -0
- package/dist/world/support.js +75 -0
- package/dist/world/terraform.d.ts +54 -0
- package/dist/world/terraform.js +186 -0
- package/dist/world/terrain.d.ts +23 -1
- package/dist/world/terrain.js +64 -1
- package/dist/world/voxelField.d.ts +54 -0
- package/dist/world/voxelField.js +241 -0
- package/dist/world/walls.d.ts +61 -0
- package/dist/world/walls.js +221 -0
- package/dist/world/weather.d.ts +92 -0
- package/dist/world/weather.js +169 -0
- package/llms.txt +4102 -0
- package/package.json +3 -2
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
export function emptyTile() {
|
|
2
|
+
return { soil: "untilled", watered: false, cropId: null, stage: 0, stageProgress: 0, harvestable: false };
|
|
3
|
+
}
|
|
4
|
+
export function canTill(state) {
|
|
5
|
+
return state.soil === "untilled" && state.cropId === null;
|
|
6
|
+
}
|
|
7
|
+
export function tillTile(state) {
|
|
8
|
+
if (!canTill(state))
|
|
9
|
+
return state;
|
|
10
|
+
return { ...state, soil: "tilled" };
|
|
11
|
+
}
|
|
12
|
+
export function canPlant(state) {
|
|
13
|
+
return state.soil === "tilled" && state.cropId === null;
|
|
14
|
+
}
|
|
15
|
+
export function plantCrop(state, cropId) {
|
|
16
|
+
if (!canPlant(state))
|
|
17
|
+
return state;
|
|
18
|
+
return { ...state, cropId, stage: 0, stageProgress: 0, harvestable: false };
|
|
19
|
+
}
|
|
20
|
+
export function waterTile(state) {
|
|
21
|
+
if (state.soil !== "tilled" || state.watered)
|
|
22
|
+
return state;
|
|
23
|
+
return { ...state, watered: true };
|
|
24
|
+
}
|
|
25
|
+
export function maturityDays(def) {
|
|
26
|
+
return def.stages.reduce((sum, days) => sum + Math.max(0, days), 0);
|
|
27
|
+
}
|
|
28
|
+
export function advanceCropDay(def, state) {
|
|
29
|
+
const rested = state.watered ? { ...state, watered: false } : state;
|
|
30
|
+
if (state.cropId === null)
|
|
31
|
+
return rested;
|
|
32
|
+
const needsWater = def.needsDailyWater !== false;
|
|
33
|
+
if (needsWater && !state.watered)
|
|
34
|
+
return rested;
|
|
35
|
+
if (state.stage < def.stages.length) {
|
|
36
|
+
const threshold = Math.max(1, def.stages[state.stage] ?? 1);
|
|
37
|
+
const stageProgress = state.stageProgress + 1;
|
|
38
|
+
if (stageProgress < threshold)
|
|
39
|
+
return { ...rested, stageProgress };
|
|
40
|
+
const stage = state.stage + 1;
|
|
41
|
+
const harvestable = stage >= def.stages.length;
|
|
42
|
+
return { ...rested, stage, stageProgress: 0, harvestable };
|
|
43
|
+
}
|
|
44
|
+
if (!state.harvestable && def.regrowDays !== undefined && def.regrowDays > 0) {
|
|
45
|
+
const stageProgress = state.stageProgress + 1;
|
|
46
|
+
if (stageProgress < def.regrowDays)
|
|
47
|
+
return { ...rested, stageProgress };
|
|
48
|
+
return { ...rested, stageProgress: 0, harvestable: true };
|
|
49
|
+
}
|
|
50
|
+
return rested;
|
|
51
|
+
}
|
|
52
|
+
export function harvestCrop(def, state) {
|
|
53
|
+
if (!state.harvestable || state.cropId === null)
|
|
54
|
+
return { state, yield: null };
|
|
55
|
+
const produce = def.harvest ?? { itemId: state.cropId, count: 1 };
|
|
56
|
+
if (def.regrowDays !== undefined && def.regrowDays > 0) {
|
|
57
|
+
return {
|
|
58
|
+
state: { ...state, harvestable: false, stageProgress: 0, stage: def.stages.length },
|
|
59
|
+
yield: produce,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
state: { ...state, soil: "tilled", cropId: null, stage: 0, stageProgress: 0, harvestable: false },
|
|
64
|
+
yield: produce,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
export function tileKey(coord) {
|
|
68
|
+
return `${coord[0]},${coord[1]}`;
|
|
69
|
+
}
|
|
70
|
+
export function singleTile() {
|
|
71
|
+
return [[0, 0]];
|
|
72
|
+
}
|
|
73
|
+
export function squarePattern(radius) {
|
|
74
|
+
const r = Math.max(0, Math.floor(radius));
|
|
75
|
+
const tiles = [];
|
|
76
|
+
for (let dx = -r; dx <= r; dx++)
|
|
77
|
+
for (let dz = -r; dz <= r; dz++)
|
|
78
|
+
tiles.push([dx, dz]);
|
|
79
|
+
return tiles;
|
|
80
|
+
}
|
|
81
|
+
export function diamondPattern(radius) {
|
|
82
|
+
const r = Math.max(0, Math.floor(radius));
|
|
83
|
+
const tiles = [];
|
|
84
|
+
for (let dx = -r; dx <= r; dx++)
|
|
85
|
+
for (let dz = -r; dz <= r; dz++)
|
|
86
|
+
if (Math.abs(dx) + Math.abs(dz) <= r)
|
|
87
|
+
tiles.push([dx, dz]);
|
|
88
|
+
return tiles;
|
|
89
|
+
}
|
|
90
|
+
export function rectPattern(width, depth) {
|
|
91
|
+
const w = Math.max(1, Math.floor(width));
|
|
92
|
+
const d = Math.max(1, Math.floor(depth));
|
|
93
|
+
const x0 = -Math.floor((w - 1) / 2);
|
|
94
|
+
const z0 = -Math.floor((d - 1) / 2);
|
|
95
|
+
const tiles = [];
|
|
96
|
+
for (let dx = 0; dx < w; dx++)
|
|
97
|
+
for (let dz = 0; dz < d; dz++)
|
|
98
|
+
tiles.push([x0 + dx, z0 + dz]);
|
|
99
|
+
return tiles;
|
|
100
|
+
}
|
|
101
|
+
export function patternAt(center, pattern) {
|
|
102
|
+
return pattern.map(([dx, dz]) => [center[0] + dx, center[1] + dz]);
|
|
103
|
+
}
|
|
104
|
+
export function applyToolToTiles(tiles, center, pattern, apply, fill = emptyTile) {
|
|
105
|
+
const next = new Map(tiles);
|
|
106
|
+
const changed = [];
|
|
107
|
+
for (const coord of patternAt(center, pattern)) {
|
|
108
|
+
const key = tileKey(coord);
|
|
109
|
+
const before = next.get(key) ?? fill();
|
|
110
|
+
const after = apply(before, coord);
|
|
111
|
+
if (after !== before) {
|
|
112
|
+
next.set(key, after);
|
|
113
|
+
changed.push(coord);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return { tiles: next, changed };
|
|
117
|
+
}
|
|
118
|
+
export function createDayTicker(startDay = 0) {
|
|
119
|
+
let last = Math.floor(startDay);
|
|
120
|
+
return {
|
|
121
|
+
tick(currentDay) {
|
|
122
|
+
const now = Math.floor(currentDay);
|
|
123
|
+
const crossed = Math.max(0, now - last);
|
|
124
|
+
last = now;
|
|
125
|
+
return crossed;
|
|
126
|
+
},
|
|
127
|
+
day: () => last,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
export function createCropField(catalog = () => null) {
|
|
131
|
+
let grid = new Map();
|
|
132
|
+
function read(coord) {
|
|
133
|
+
return grid.get(tileKey(coord)) ?? emptyTile();
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
get: read,
|
|
137
|
+
till(coord, pattern = singleTile()) {
|
|
138
|
+
const result = applyToolToTiles(grid, coord, pattern, tillTile);
|
|
139
|
+
grid = result.tiles;
|
|
140
|
+
return result.changed;
|
|
141
|
+
},
|
|
142
|
+
plant(coord, cropId) {
|
|
143
|
+
const before = read(coord);
|
|
144
|
+
const after = plantCrop(before, cropId);
|
|
145
|
+
if (after === before)
|
|
146
|
+
return false;
|
|
147
|
+
grid.set(tileKey(coord), after);
|
|
148
|
+
return true;
|
|
149
|
+
},
|
|
150
|
+
water(coord, pattern = singleTile()) {
|
|
151
|
+
const result = applyToolToTiles(grid, coord, pattern, waterTile);
|
|
152
|
+
grid = result.tiles;
|
|
153
|
+
return result.changed;
|
|
154
|
+
},
|
|
155
|
+
harvest(coord) {
|
|
156
|
+
const state = read(coord);
|
|
157
|
+
if (state.cropId === null)
|
|
158
|
+
return null;
|
|
159
|
+
const def = catalog(state.cropId);
|
|
160
|
+
if (def === null)
|
|
161
|
+
return null;
|
|
162
|
+
const result = harvestCrop(def, state);
|
|
163
|
+
grid.set(tileKey(coord), result.state);
|
|
164
|
+
return result.yield;
|
|
165
|
+
},
|
|
166
|
+
advanceDay(days = 1) {
|
|
167
|
+
for (let i = 0; i < days; i++) {
|
|
168
|
+
for (const [key, state] of grid) {
|
|
169
|
+
if (state.cropId === null) {
|
|
170
|
+
if (state.watered)
|
|
171
|
+
grid.set(key, { ...state, watered: false });
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
const def = catalog(state.cropId);
|
|
175
|
+
grid.set(key, def === null ? state : advanceCropDay(def, state));
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
tiles: () => grid,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { RecipeItem } from "./recipe.js";
|
|
2
|
+
export interface ProductionBuildingDef {
|
|
3
|
+
id: string;
|
|
4
|
+
inputs: readonly RecipeItem[];
|
|
5
|
+
outputs: readonly RecipeItem[];
|
|
6
|
+
rate: number;
|
|
7
|
+
power?: number;
|
|
8
|
+
bufferMultiplier?: number;
|
|
9
|
+
}
|
|
10
|
+
export interface ProductionBuildingConfig {
|
|
11
|
+
id: string;
|
|
12
|
+
inputs?: readonly RecipeItem[];
|
|
13
|
+
outputs: readonly RecipeItem[];
|
|
14
|
+
rate: number;
|
|
15
|
+
power?: number;
|
|
16
|
+
bufferMultiplier?: number;
|
|
17
|
+
}
|
|
18
|
+
export type ItemCounts = Readonly<Record<string, number>>;
|
|
19
|
+
export interface ProductionState {
|
|
20
|
+
buffer: ItemCounts;
|
|
21
|
+
output: ItemCounts;
|
|
22
|
+
progress: number;
|
|
23
|
+
active: boolean;
|
|
24
|
+
}
|
|
25
|
+
export interface ProductionTickInput {
|
|
26
|
+
dt: number;
|
|
27
|
+
powered?: boolean;
|
|
28
|
+
}
|
|
29
|
+
export declare function productionBuilding(config: ProductionBuildingConfig): ProductionBuildingDef;
|
|
30
|
+
export declare function createProductionState(): ProductionState;
|
|
31
|
+
export declare function acceptsInput(def: ProductionBuildingDef, state: ProductionState, itemId: string): boolean;
|
|
32
|
+
export declare function feedProduction(def: ProductionBuildingDef, state: ProductionState, itemId: string, count: number): {
|
|
33
|
+
state: ProductionState;
|
|
34
|
+
accepted: number;
|
|
35
|
+
};
|
|
36
|
+
export declare function drainOutput(state: ProductionState, itemId: string, count?: number): {
|
|
37
|
+
state: ProductionState;
|
|
38
|
+
taken: number;
|
|
39
|
+
};
|
|
40
|
+
export declare function tickProduction(def: ProductionBuildingDef, state: ProductionState, input: ProductionTickInput): ProductionState;
|
|
41
|
+
export interface TransportItem {
|
|
42
|
+
itemId: string;
|
|
43
|
+
count: number;
|
|
44
|
+
position: number;
|
|
45
|
+
}
|
|
46
|
+
export interface TransportPath {
|
|
47
|
+
length: number;
|
|
48
|
+
speed: number;
|
|
49
|
+
}
|
|
50
|
+
export declare function advanceTransport(path: TransportPath, items: readonly TransportItem[], dt: number): {
|
|
51
|
+
items: TransportItem[];
|
|
52
|
+
delivered: TransportItem[];
|
|
53
|
+
};
|
|
54
|
+
export interface PowerConsumer {
|
|
55
|
+
id: string;
|
|
56
|
+
demand: number;
|
|
57
|
+
}
|
|
58
|
+
export interface PowerGridResult {
|
|
59
|
+
powered: ReadonlySet<string>;
|
|
60
|
+
supply: number;
|
|
61
|
+
demand: number;
|
|
62
|
+
deficit: number;
|
|
63
|
+
}
|
|
64
|
+
export declare function resolvePowerGrid(supply: number, consumers: readonly PowerConsumer[]): PowerGridResult;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
export function productionBuilding(config) {
|
|
2
|
+
return {
|
|
3
|
+
id: config.id,
|
|
4
|
+
inputs: config.inputs ?? [],
|
|
5
|
+
outputs: config.outputs,
|
|
6
|
+
rate: config.rate,
|
|
7
|
+
power: config.power,
|
|
8
|
+
bufferMultiplier: config.bufferMultiplier,
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export function createProductionState() {
|
|
12
|
+
return { buffer: {}, output: {}, progress: 0, active: false };
|
|
13
|
+
}
|
|
14
|
+
function addCount(counts, itemId, amount) {
|
|
15
|
+
const next = { ...counts };
|
|
16
|
+
const total = (next[itemId] ?? 0) + amount;
|
|
17
|
+
if (total <= 0)
|
|
18
|
+
delete next[itemId];
|
|
19
|
+
else
|
|
20
|
+
next[itemId] = total;
|
|
21
|
+
return next;
|
|
22
|
+
}
|
|
23
|
+
function bufferCapacity(def, required) {
|
|
24
|
+
const multiplier = def.bufferMultiplier !== undefined && def.bufferMultiplier > 0 ? def.bufferMultiplier : 2;
|
|
25
|
+
return Math.max(required, Math.ceil(required * multiplier));
|
|
26
|
+
}
|
|
27
|
+
export function acceptsInput(def, state, itemId) {
|
|
28
|
+
const input = def.inputs.find((i) => i.itemId === itemId);
|
|
29
|
+
if (input === undefined)
|
|
30
|
+
return false;
|
|
31
|
+
const held = state.buffer[itemId] ?? 0;
|
|
32
|
+
return held < bufferCapacity(def, input.count);
|
|
33
|
+
}
|
|
34
|
+
export function feedProduction(def, state, itemId, count) {
|
|
35
|
+
const input = def.inputs.find((i) => i.itemId === itemId);
|
|
36
|
+
if (input === undefined || count <= 0)
|
|
37
|
+
return { state, accepted: 0 };
|
|
38
|
+
const capacity = bufferCapacity(def, input.count);
|
|
39
|
+
const held = state.buffer[itemId] ?? 0;
|
|
40
|
+
const accepted = Math.min(count, Math.max(0, capacity - held));
|
|
41
|
+
if (accepted === 0)
|
|
42
|
+
return { state, accepted: 0 };
|
|
43
|
+
return { state: { ...state, buffer: addCount(state.buffer, itemId, accepted) }, accepted };
|
|
44
|
+
}
|
|
45
|
+
export function drainOutput(state, itemId, count) {
|
|
46
|
+
const held = state.output[itemId] ?? 0;
|
|
47
|
+
const taken = count === undefined ? held : Math.min(count, held);
|
|
48
|
+
if (taken <= 0)
|
|
49
|
+
return { state, taken: 0 };
|
|
50
|
+
return { state: { ...state, output: addCount(state.output, itemId, -taken) }, taken };
|
|
51
|
+
}
|
|
52
|
+
function hasInputs(def, buffer) {
|
|
53
|
+
return def.inputs.every((i) => (buffer[i.itemId] ?? 0) >= i.count);
|
|
54
|
+
}
|
|
55
|
+
function consume(def, buffer) {
|
|
56
|
+
let next = buffer;
|
|
57
|
+
for (const input of def.inputs)
|
|
58
|
+
next = addCount(next, input.itemId, -input.count);
|
|
59
|
+
return next;
|
|
60
|
+
}
|
|
61
|
+
function emit(def, output) {
|
|
62
|
+
let next = output;
|
|
63
|
+
for (const out of def.outputs)
|
|
64
|
+
next = addCount(next, out.itemId, out.count);
|
|
65
|
+
return next;
|
|
66
|
+
}
|
|
67
|
+
export function tickProduction(def, state, input) {
|
|
68
|
+
const powerNeeded = def.power !== undefined && def.power > 0;
|
|
69
|
+
if (powerNeeded && input.powered !== true)
|
|
70
|
+
return state;
|
|
71
|
+
if (!(def.rate > 0) || input.dt <= 0)
|
|
72
|
+
return state;
|
|
73
|
+
const secondsPerCycle = 1 / def.rate;
|
|
74
|
+
let buffer = state.buffer;
|
|
75
|
+
let output = state.output;
|
|
76
|
+
let progress = state.progress;
|
|
77
|
+
let active = state.active;
|
|
78
|
+
if (!active) {
|
|
79
|
+
if (!hasInputs(def, buffer))
|
|
80
|
+
return state;
|
|
81
|
+
buffer = consume(def, buffer);
|
|
82
|
+
active = true;
|
|
83
|
+
progress = 0;
|
|
84
|
+
}
|
|
85
|
+
progress += input.dt;
|
|
86
|
+
let guard = 0;
|
|
87
|
+
while (active && progress >= secondsPerCycle) {
|
|
88
|
+
progress -= secondsPerCycle;
|
|
89
|
+
output = emit(def, output);
|
|
90
|
+
if (hasInputs(def, buffer)) {
|
|
91
|
+
buffer = consume(def, buffer);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
active = false;
|
|
95
|
+
progress = 0;
|
|
96
|
+
}
|
|
97
|
+
guard += 1;
|
|
98
|
+
if (guard >= 10_000)
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
return { buffer, output, progress, active };
|
|
102
|
+
}
|
|
103
|
+
export function advanceTransport(path, items, dt) {
|
|
104
|
+
if (dt <= 0 || !(path.speed > 0))
|
|
105
|
+
return { items: items.slice(), delivered: [] };
|
|
106
|
+
const moving = [];
|
|
107
|
+
const delivered = [];
|
|
108
|
+
const step = path.speed * dt;
|
|
109
|
+
for (const item of items) {
|
|
110
|
+
const position = item.position + step;
|
|
111
|
+
if (position >= path.length)
|
|
112
|
+
delivered.push({ ...item, position: path.length });
|
|
113
|
+
else
|
|
114
|
+
moving.push({ ...item, position });
|
|
115
|
+
}
|
|
116
|
+
return { items: moving, delivered };
|
|
117
|
+
}
|
|
118
|
+
export function resolvePowerGrid(supply, consumers) {
|
|
119
|
+
const powered = new Set();
|
|
120
|
+
let used = 0;
|
|
121
|
+
let demand = 0;
|
|
122
|
+
for (const consumer of consumers) {
|
|
123
|
+
demand += Math.max(0, consumer.demand);
|
|
124
|
+
if (used + consumer.demand <= supply) {
|
|
125
|
+
used += consumer.demand;
|
|
126
|
+
powered.add(consumer.id);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return { powered, supply, demand, deficit: Math.max(0, demand - supply) };
|
|
130
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { Vec2 } from "../world/geometry.js";
|
|
2
|
+
import type { InventoryLayout, InventoryState, ItemTraits } from "../inventory/inventoryModel.js";
|
|
3
|
+
export interface RecipeItem {
|
|
4
|
+
itemId: string;
|
|
5
|
+
count: number;
|
|
6
|
+
}
|
|
7
|
+
export interface RecipeDef {
|
|
8
|
+
id: string;
|
|
9
|
+
inputs: readonly RecipeItem[];
|
|
10
|
+
outputs: readonly RecipeItem[];
|
|
11
|
+
seconds?: number;
|
|
12
|
+
station?: string;
|
|
13
|
+
stationRange?: number;
|
|
14
|
+
requires?: readonly string[];
|
|
15
|
+
category?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface StationInstance {
|
|
18
|
+
catalogId: string;
|
|
19
|
+
position: Vec2;
|
|
20
|
+
}
|
|
21
|
+
export interface CraftContext {
|
|
22
|
+
origin?: Vec2;
|
|
23
|
+
stations?: readonly StationInstance[];
|
|
24
|
+
unlocked?: (id: string) => boolean;
|
|
25
|
+
}
|
|
26
|
+
export type CraftRejection = {
|
|
27
|
+
reason: "missing-inputs";
|
|
28
|
+
missing: readonly RecipeItem[];
|
|
29
|
+
} | {
|
|
30
|
+
reason: "no-station";
|
|
31
|
+
station: string;
|
|
32
|
+
} | {
|
|
33
|
+
reason: "locked";
|
|
34
|
+
requires: readonly string[];
|
|
35
|
+
} | {
|
|
36
|
+
reason: "no-output-space";
|
|
37
|
+
};
|
|
38
|
+
export type CraftCheck = {
|
|
39
|
+
ok: true;
|
|
40
|
+
} | ({
|
|
41
|
+
ok: false;
|
|
42
|
+
} & CraftRejection);
|
|
43
|
+
export type CraftResult = {
|
|
44
|
+
status: "ok";
|
|
45
|
+
state: InventoryState;
|
|
46
|
+
} | ({
|
|
47
|
+
status: "rejected";
|
|
48
|
+
} & CraftRejection);
|
|
49
|
+
export declare function craftSeconds(recipe: RecipeDef): number;
|
|
50
|
+
export declare function missingInputs(state: InventoryState, recipe: RecipeDef): RecipeItem[];
|
|
51
|
+
export declare function hasRecipeInputs(state: InventoryState, recipe: RecipeDef): boolean;
|
|
52
|
+
export declare function stationSatisfied(recipe: RecipeDef, context: CraftContext): boolean;
|
|
53
|
+
export declare function canCraft(state: InventoryState, layout: InventoryLayout, traits: ItemTraits, recipe: RecipeDef, context?: CraftContext): CraftCheck;
|
|
54
|
+
export declare function craft(state: InventoryState, layout: InventoryLayout, traits: ItemTraits, recipe: RecipeDef, context?: CraftContext): CraftResult;
|
|
55
|
+
export interface RecipeGraph {
|
|
56
|
+
all(): readonly RecipeDef[];
|
|
57
|
+
get(id: string): RecipeDef | null;
|
|
58
|
+
producing(itemId: string): RecipeDef[];
|
|
59
|
+
using(itemId: string): RecipeDef[];
|
|
60
|
+
category(categoryId: string): RecipeDef[];
|
|
61
|
+
}
|
|
62
|
+
export declare function createRecipeGraph(defs?: readonly RecipeDef[]): RecipeGraph;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { countItem, putItem, takeItem } from "../inventory/inventoryModel.js";
|
|
2
|
+
export function craftSeconds(recipe) {
|
|
3
|
+
return recipe.seconds !== undefined && recipe.seconds > 0 ? recipe.seconds : 0;
|
|
4
|
+
}
|
|
5
|
+
export function missingInputs(state, recipe) {
|
|
6
|
+
const missing = [];
|
|
7
|
+
for (const input of recipe.inputs) {
|
|
8
|
+
const have = countItem(state, input.itemId);
|
|
9
|
+
if (have < input.count)
|
|
10
|
+
missing.push({ itemId: input.itemId, count: input.count - have });
|
|
11
|
+
}
|
|
12
|
+
return missing;
|
|
13
|
+
}
|
|
14
|
+
export function hasRecipeInputs(state, recipe) {
|
|
15
|
+
return missingInputs(state, recipe).length === 0;
|
|
16
|
+
}
|
|
17
|
+
export function stationSatisfied(recipe, context) {
|
|
18
|
+
if (recipe.station === undefined)
|
|
19
|
+
return true;
|
|
20
|
+
const range = recipe.stationRange !== undefined && recipe.stationRange > 0 ? recipe.stationRange : Infinity;
|
|
21
|
+
const origin = context.origin;
|
|
22
|
+
const stations = context.stations ?? [];
|
|
23
|
+
for (const station of stations) {
|
|
24
|
+
if (station.catalogId !== recipe.station)
|
|
25
|
+
continue;
|
|
26
|
+
if (range === Infinity || origin === undefined)
|
|
27
|
+
return true;
|
|
28
|
+
const dx = station.position[0] - origin[0];
|
|
29
|
+
const dz = station.position[1] - origin[1];
|
|
30
|
+
if (dx * dx + dz * dz <= range * range)
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
function lockedRequirements(recipe, context) {
|
|
36
|
+
if (recipe.requires === undefined || recipe.requires.length === 0)
|
|
37
|
+
return [];
|
|
38
|
+
const unlocked = context.unlocked;
|
|
39
|
+
if (unlocked === undefined)
|
|
40
|
+
return [];
|
|
41
|
+
return recipe.requires.filter((id) => !unlocked(id));
|
|
42
|
+
}
|
|
43
|
+
function outputsFit(state, layout, traits, recipe) {
|
|
44
|
+
let projected = state;
|
|
45
|
+
for (const output of recipe.outputs) {
|
|
46
|
+
const put = putItem(projected, layout, traits, output.itemId, output.count);
|
|
47
|
+
if (put.status !== "ok")
|
|
48
|
+
return false;
|
|
49
|
+
projected = put.state;
|
|
50
|
+
}
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
function consumedState(state, recipe) {
|
|
54
|
+
let projected = state;
|
|
55
|
+
for (const input of recipe.inputs) {
|
|
56
|
+
const take = takeItem(projected, input.itemId, input.count);
|
|
57
|
+
if (take.status === "ok")
|
|
58
|
+
projected = take.state;
|
|
59
|
+
}
|
|
60
|
+
return projected;
|
|
61
|
+
}
|
|
62
|
+
export function canCraft(state, layout, traits, recipe, context = {}) {
|
|
63
|
+
const locked = lockedRequirements(recipe, context);
|
|
64
|
+
if (locked.length > 0)
|
|
65
|
+
return { ok: false, reason: "locked", requires: locked };
|
|
66
|
+
if (!stationSatisfied(recipe, context))
|
|
67
|
+
return { ok: false, reason: "no-station", station: recipe.station };
|
|
68
|
+
const missing = missingInputs(state, recipe);
|
|
69
|
+
if (missing.length > 0)
|
|
70
|
+
return { ok: false, reason: "missing-inputs", missing };
|
|
71
|
+
if (!outputsFit(consumedState(state, recipe), layout, traits, recipe))
|
|
72
|
+
return { ok: false, reason: "no-output-space" };
|
|
73
|
+
return { ok: true };
|
|
74
|
+
}
|
|
75
|
+
export function craft(state, layout, traits, recipe, context = {}) {
|
|
76
|
+
const check = canCraft(state, layout, traits, recipe, context);
|
|
77
|
+
if (!check.ok) {
|
|
78
|
+
const { ok: _ok, ...rejection } = check;
|
|
79
|
+
return { status: "rejected", ...rejection };
|
|
80
|
+
}
|
|
81
|
+
let projected = consumedState(state, recipe);
|
|
82
|
+
for (const output of recipe.outputs) {
|
|
83
|
+
const put = putItem(projected, layout, traits, output.itemId, output.count);
|
|
84
|
+
if (put.status !== "ok")
|
|
85
|
+
return { status: "rejected", reason: "no-output-space" };
|
|
86
|
+
projected = put.state;
|
|
87
|
+
}
|
|
88
|
+
return { status: "ok", state: projected };
|
|
89
|
+
}
|
|
90
|
+
export function createRecipeGraph(defs = []) {
|
|
91
|
+
const byId = new Map();
|
|
92
|
+
for (const def of defs)
|
|
93
|
+
byId.set(def.id, def);
|
|
94
|
+
return {
|
|
95
|
+
all: () => defs,
|
|
96
|
+
get: (id) => byId.get(id) ?? null,
|
|
97
|
+
producing: (itemId) => defs.filter((r) => r.outputs.some((o) => o.itemId === itemId)),
|
|
98
|
+
using: (itemId) => defs.filter((r) => r.inputs.some((i) => i.itemId === itemId)),
|
|
99
|
+
category: (categoryId) => defs.filter((r) => r.category === categoryId),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export type DataSourceStatus = "idle" | "loading" | "ready" | "error";
|
|
2
|
+
export interface DataSourceState<T> {
|
|
3
|
+
readonly status: DataSourceStatus;
|
|
4
|
+
readonly data: T | undefined;
|
|
5
|
+
readonly error: Error | undefined;
|
|
6
|
+
}
|
|
7
|
+
export interface DataSourceClock {
|
|
8
|
+
setInterval(handler: () => void, intervalMs: number): unknown;
|
|
9
|
+
clearInterval(handle: unknown): void;
|
|
10
|
+
}
|
|
11
|
+
export interface DataSourceOptions {
|
|
12
|
+
intervalMs?: number;
|
|
13
|
+
clock?: DataSourceClock;
|
|
14
|
+
}
|
|
15
|
+
export interface RefreshOptions {
|
|
16
|
+
force?: boolean;
|
|
17
|
+
}
|
|
18
|
+
export interface DataSource<T> {
|
|
19
|
+
getState(): DataSourceState<T>;
|
|
20
|
+
subscribe(listener: (state: DataSourceState<T>) => void): () => void;
|
|
21
|
+
refresh(options?: RefreshOptions): Promise<void>;
|
|
22
|
+
startPolling(intervalMs?: number): void;
|
|
23
|
+
stopPolling(): void;
|
|
24
|
+
dispose(): void;
|
|
25
|
+
}
|
|
26
|
+
export declare function createDataSource<T>(load: (signal: AbortSignal) => Promise<T>, options?: DataSourceOptions): DataSource<T>;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
const defaultClock = {
|
|
2
|
+
setInterval: (handler, intervalMs) => globalThis.setInterval(handler, intervalMs),
|
|
3
|
+
clearInterval: (handle) => globalThis.clearInterval(handle),
|
|
4
|
+
};
|
|
5
|
+
export function createDataSource(load, options = {}) {
|
|
6
|
+
const clock = options.clock ?? defaultClock;
|
|
7
|
+
const listeners = new Set();
|
|
8
|
+
let state = { status: "idle", data: undefined, error: undefined };
|
|
9
|
+
let controller;
|
|
10
|
+
let inFlight;
|
|
11
|
+
let pollHandle;
|
|
12
|
+
let disposed = false;
|
|
13
|
+
function setState(next) {
|
|
14
|
+
state = next;
|
|
15
|
+
for (const listener of listeners)
|
|
16
|
+
listener(state);
|
|
17
|
+
}
|
|
18
|
+
async function run() {
|
|
19
|
+
const ownController = new AbortController();
|
|
20
|
+
controller = ownController;
|
|
21
|
+
setState({ status: "loading", data: state.data, error: undefined });
|
|
22
|
+
try {
|
|
23
|
+
const data = await load(ownController.signal);
|
|
24
|
+
if (ownController.signal.aborted)
|
|
25
|
+
return;
|
|
26
|
+
setState({ status: "ready", data, error: undefined });
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
if (ownController.signal.aborted)
|
|
30
|
+
return;
|
|
31
|
+
setState({
|
|
32
|
+
status: "error",
|
|
33
|
+
data: state.data,
|
|
34
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
finally {
|
|
38
|
+
if (controller === ownController)
|
|
39
|
+
controller = undefined;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function refresh(refreshOptions = {}) {
|
|
43
|
+
if (disposed)
|
|
44
|
+
return Promise.resolve();
|
|
45
|
+
if (inFlight !== undefined) {
|
|
46
|
+
if (!refreshOptions.force)
|
|
47
|
+
return inFlight;
|
|
48
|
+
controller?.abort();
|
|
49
|
+
}
|
|
50
|
+
const promise = run().finally(() => {
|
|
51
|
+
if (inFlight === promise)
|
|
52
|
+
inFlight = undefined;
|
|
53
|
+
});
|
|
54
|
+
inFlight = promise;
|
|
55
|
+
return promise;
|
|
56
|
+
}
|
|
57
|
+
function stopPolling() {
|
|
58
|
+
if (pollHandle !== undefined) {
|
|
59
|
+
clock.clearInterval(pollHandle);
|
|
60
|
+
pollHandle = undefined;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function startPolling(intervalMs) {
|
|
64
|
+
stopPolling();
|
|
65
|
+
const resolvedInterval = intervalMs ?? options.intervalMs;
|
|
66
|
+
if (resolvedInterval === undefined) {
|
|
67
|
+
throw new Error("startPolling requires an intervalMs (pass one, or set options.intervalMs at creation)");
|
|
68
|
+
}
|
|
69
|
+
void refresh();
|
|
70
|
+
pollHandle = clock.setInterval(() => {
|
|
71
|
+
void refresh();
|
|
72
|
+
}, resolvedInterval);
|
|
73
|
+
}
|
|
74
|
+
function dispose() {
|
|
75
|
+
disposed = true;
|
|
76
|
+
stopPolling();
|
|
77
|
+
controller?.abort();
|
|
78
|
+
listeners.clear();
|
|
79
|
+
}
|
|
80
|
+
if (options.intervalMs !== undefined)
|
|
81
|
+
startPolling(options.intervalMs);
|
|
82
|
+
return {
|
|
83
|
+
getState: () => state,
|
|
84
|
+
subscribe(listener) {
|
|
85
|
+
listeners.add(listener);
|
|
86
|
+
return () => listeners.delete(listener);
|
|
87
|
+
},
|
|
88
|
+
refresh,
|
|
89
|
+
startPolling,
|
|
90
|
+
stopPolling,
|
|
91
|
+
dispose,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface DevProxyTable {
|
|
2
|
+
readonly [routeName: string]: string;
|
|
3
|
+
}
|
|
4
|
+
export interface ProxiedUrlOptions {
|
|
5
|
+
dev?: boolean;
|
|
6
|
+
table?: DevProxyTable;
|
|
7
|
+
prefix?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function parseDevProxyTable(raw: string | undefined): DevProxyTable;
|
|
10
|
+
export declare const DEFAULT_DEV_PROXY_PREFIX = "/proxy";
|
|
11
|
+
export declare function proxiedUrl(target: string, options?: ProxiedUrlOptions): string;
|