@shipload/sdk 1.0.0-next.26 → 1.0.0-next.27
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/lib/shipload.d.ts +193 -85
- package/lib/shipload.js +775 -378
- package/lib/shipload.js.map +1 -1
- package/lib/shipload.m.js +764 -377
- package/lib/shipload.m.js.map +1 -1
- package/lib/testing.d.ts +13 -8
- package/lib/testing.js +38 -23
- package/lib/testing.js.map +1 -1
- package/lib/testing.m.js +38 -23
- package/lib/testing.m.js.map +1 -1
- package/package.json +1 -1
- package/src/capabilities/craftable.ts +51 -0
- package/src/contracts/server.ts +39 -18
- package/src/data/capabilities.ts +5 -0
- package/src/entities/entity.ts +1 -1
- package/src/entities/makers.ts +14 -5
- package/src/index-module.ts +24 -4
- package/src/managers/actions.ts +10 -1
- package/src/managers/construction.ts +67 -65
- package/src/scheduling/accessor.ts +65 -23
- package/src/scheduling/availability.ts +108 -0
- package/src/scheduling/energy.ts +18 -11
- package/src/scheduling/lane-core.ts +130 -0
- package/src/scheduling/lanes.ts +60 -0
- package/src/scheduling/projection.ts +30 -54
- package/src/scheduling/schedule.ts +236 -121
- package/src/travel/travel.ts +21 -16
- package/src/types/capabilities.ts +1 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type {ServerContract} from '../contracts'
|
|
2
|
+
import {getItem} from '../data/catalog'
|
|
3
|
+
import type {ModuleType} from '../types'
|
|
4
|
+
import {getLane, type ScheduleData} from './schedule'
|
|
5
|
+
|
|
6
|
+
type ModuleEntry = ServerContract.Types.module_entry
|
|
7
|
+
type Lane = ServerContract.Types.lane
|
|
8
|
+
type Schedule = ServerContract.Types.schedule
|
|
9
|
+
|
|
10
|
+
export function laneKeyForModule(slotIndex: number): number {
|
|
11
|
+
return slotIndex + 1
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function laneIsFree(lanes: Lane[], laneKey: number): boolean {
|
|
15
|
+
const lane = lanes.find((entry) => entry.lane_key.toNumber() === laneKey)
|
|
16
|
+
return lane ? lane.schedule.tasks.length === 0 : true
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function workerLaneKey(
|
|
20
|
+
modules: ModuleEntry[],
|
|
21
|
+
moduleSubtype: ModuleType,
|
|
22
|
+
lanes: Lane[]
|
|
23
|
+
): number {
|
|
24
|
+
const occupiedMatchingLaneKeys: number[] = []
|
|
25
|
+
|
|
26
|
+
for (let slotIndex = 0; slotIndex < modules.length; slotIndex++) {
|
|
27
|
+
const installed = modules[slotIndex].installed
|
|
28
|
+
if (!installed) continue
|
|
29
|
+
if (getItem(installed.item_id).moduleType !== moduleSubtype) continue
|
|
30
|
+
|
|
31
|
+
const laneKey = laneKeyForModule(slotIndex)
|
|
32
|
+
if (laneIsFree(lanes, laneKey)) return laneKey
|
|
33
|
+
occupiedMatchingLaneKeys.push(laneKey)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (occupiedMatchingLaneKeys.length > 0) {
|
|
37
|
+
return Math.min(...occupiedMatchingLaneKeys)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
throw new Error(`No installed ${moduleSubtype} worker module`)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function rawScheduleEnd(schedule: Schedule): Date {
|
|
44
|
+
const durationSec = schedule.tasks.reduce((sum, task) => sum + task.duration.toNumber(), 0)
|
|
45
|
+
return new Date(schedule.started.toDate().getTime() + durationSec * 1000)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function candidateLaneCompletesAt(
|
|
49
|
+
entity: ScheduleData,
|
|
50
|
+
laneKey: number,
|
|
51
|
+
durationSec: number,
|
|
52
|
+
now: Date
|
|
53
|
+
): Date {
|
|
54
|
+
const lane = getLane(entity, laneKey)
|
|
55
|
+
const startMs = lane
|
|
56
|
+
? Math.max(rawScheduleEnd(lane.schedule).getTime(), now.getTime())
|
|
57
|
+
: now.getTime()
|
|
58
|
+
|
|
59
|
+
return new Date(startMs + durationSec * 1000)
|
|
60
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {Name,
|
|
1
|
+
import {Name, UInt16, UInt32, UInt64} from '@wharfkit/antelope'
|
|
2
2
|
import {ServerContract} from '../contracts'
|
|
3
3
|
import {Coordinates, TaskType} from '../types'
|
|
4
4
|
import {
|
|
@@ -333,50 +333,27 @@ export interface ProjectionOptions {
|
|
|
333
333
|
|
|
334
334
|
export function projectEntity(entity: Projectable, options?: ProjectionOptions): ProjectedEntity {
|
|
335
335
|
const projected = createProjectedEntity(entity)
|
|
336
|
-
|
|
336
|
+
const ordered = schedule.orderedTasks(entity)
|
|
337
|
+
if (ordered.length === 0) return projected
|
|
337
338
|
|
|
338
|
-
const tasks = entity.schedule.tasks
|
|
339
339
|
const taskCount =
|
|
340
340
|
options?.upToTaskIndex !== undefined
|
|
341
|
-
? Math.max(0, Math.min(options.upToTaskIndex,
|
|
342
|
-
:
|
|
341
|
+
? Math.max(0, Math.min(options.upToTaskIndex, ordered.length))
|
|
342
|
+
: ordered.length
|
|
343
343
|
|
|
344
344
|
for (let i = 0; i < taskCount; i++) {
|
|
345
|
-
applyTask(projected,
|
|
345
|
+
applyTask(projected, ordered[i].task)
|
|
346
346
|
}
|
|
347
347
|
return projected
|
|
348
348
|
}
|
|
349
349
|
|
|
350
|
-
export
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
function buildRemainingProjectable(snapshot: ProjectableSnapshot): Projectable | null {
|
|
356
|
-
if (!snapshot.schedule) return null
|
|
357
|
-
const remainingTasks: ServerContract.Types.task[] = []
|
|
358
|
-
if (snapshot.current_task) remainingTasks.push(snapshot.current_task)
|
|
359
|
-
if (snapshot.pending_tasks?.length) remainingTasks.push(...snapshot.pending_tasks)
|
|
360
|
-
if (remainingTasks.length === 0) return null
|
|
361
|
-
|
|
362
|
-
const completedCount = snapshot.schedule.tasks.length - remainingTasks.length
|
|
363
|
-
let startedMs = snapshot.schedule.started.toMilliseconds()
|
|
364
|
-
for (let i = 0; i < completedCount; i++) {
|
|
365
|
-
startedMs += snapshot.schedule.tasks[i].duration.toNumber() * 1000
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
return {
|
|
369
|
-
...snapshot,
|
|
370
|
-
schedule: ServerContract.Types.schedule.from({
|
|
371
|
-
started: TimePoint.fromMilliseconds(startedMs),
|
|
372
|
-
tasks: remainingTasks,
|
|
373
|
-
}),
|
|
350
|
+
export function projectRemainingAt(entity: Projectable, _now: Date): ProjectedEntity {
|
|
351
|
+
// Resolve is lazy/entity-global: completed tasks are unsettled until resolve, so replay all.
|
|
352
|
+
const projected = createProjectedEntity(entity)
|
|
353
|
+
for (const {task} of schedule.orderedTasks(entity)) {
|
|
354
|
+
applyTask(projected, task)
|
|
374
355
|
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
export function projectFromCurrentState(snapshot: ProjectableSnapshot): ProjectedEntity {
|
|
378
|
-
const projectable = buildRemainingProjectable(snapshot)
|
|
379
|
-
return projectable ? projectEntity(projectable) : createProjectedEntity(snapshot)
|
|
356
|
+
return projected
|
|
380
357
|
}
|
|
381
358
|
|
|
382
359
|
function getRecipeInputsForOutput(outputItemId: number): RecipeInput[] | undefined {
|
|
@@ -438,10 +415,11 @@ function validateCraftTask(task: ServerContract.Types.task, projected: Projected
|
|
|
438
415
|
}
|
|
439
416
|
|
|
440
417
|
export function validateSchedule(entity: Projectable): void {
|
|
441
|
-
|
|
418
|
+
const ordered = schedule.orderedTasks(entity)
|
|
419
|
+
if (ordered.length === 0) return
|
|
442
420
|
|
|
443
421
|
const projected = createProjectedEntity(entity)
|
|
444
|
-
for (const task of
|
|
422
|
+
for (const {task} of ordered) {
|
|
445
423
|
if (task.type.toNumber() === TaskType.CRAFT) {
|
|
446
424
|
validateCraftTask(task, projected)
|
|
447
425
|
}
|
|
@@ -455,22 +433,28 @@ export function validateSchedule(entity: Projectable): void {
|
|
|
455
433
|
export function projectEntityAt(entity: Projectable, now: Date): ProjectedEntity {
|
|
456
434
|
const projected = createProjectedEntity(entity)
|
|
457
435
|
|
|
458
|
-
|
|
436
|
+
const ordered = schedule.orderedTasks(entity)
|
|
437
|
+
if (ordered.length === 0) {
|
|
459
438
|
return projected
|
|
460
439
|
}
|
|
461
440
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
const
|
|
441
|
+
const nowMs = now.getTime()
|
|
442
|
+
|
|
443
|
+
for (const {task, startsAt} of ordered) {
|
|
444
|
+
const duration = task.duration.toNumber()
|
|
445
|
+
const isReserved = task.type.toNumber() === TaskType.RESERVED
|
|
446
|
+
const elapsed = Math.min(
|
|
447
|
+
Math.max(0, Math.floor((nowMs - startsAt.getTime()) / 1000)),
|
|
448
|
+
duration
|
|
449
|
+
)
|
|
450
|
+
const taskComplete = !isReserved && elapsed >= duration
|
|
451
|
+
const taskInProgress = elapsed > 0 && elapsed < duration
|
|
466
452
|
|
|
467
453
|
if (!taskComplete && !taskInProgress) {
|
|
468
|
-
|
|
454
|
+
continue
|
|
469
455
|
}
|
|
470
456
|
|
|
471
|
-
const progress = taskInProgress
|
|
472
|
-
? schedule.getTaskElapsed(entity, i, now) / task.duration.toNumber()
|
|
473
|
-
: undefined
|
|
457
|
+
const progress = taskInProgress ? elapsed / duration : undefined
|
|
474
458
|
|
|
475
459
|
switch (task.type.toNumber()) {
|
|
476
460
|
case TaskType.RECHARGE:
|
|
@@ -504,11 +488,3 @@ export function projectEntityAt(entity: Projectable, now: Date): ProjectedEntity
|
|
|
504
488
|
|
|
505
489
|
return projected
|
|
506
490
|
}
|
|
507
|
-
|
|
508
|
-
export function projectFromCurrentStateAt(
|
|
509
|
-
snapshot: ProjectableSnapshot,
|
|
510
|
-
now: Date
|
|
511
|
-
): ProjectedEntity {
|
|
512
|
-
const projectable = buildRemainingProjectable(snapshot)
|
|
513
|
-
return projectable ? projectEntityAt(projectable, now) : createProjectedEntity(snapshot)
|
|
514
|
-
}
|
|
@@ -1,198 +1,313 @@
|
|
|
1
1
|
import type {ServerContract} from '../contracts'
|
|
2
2
|
import {TaskType} from '../types'
|
|
3
|
+
import * as core from './lane-core'
|
|
3
4
|
|
|
4
5
|
type Schedule = ServerContract.Types.schedule
|
|
5
6
|
type Task = ServerContract.Types.task
|
|
7
|
+
type Lane = ServerContract.Types.lane
|
|
8
|
+
|
|
9
|
+
export const LANE_MOBILITY = 0
|
|
10
|
+
export const LANE_BARRIER = 255
|
|
6
11
|
|
|
7
12
|
export interface ScheduleData {
|
|
8
|
-
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export interface
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
13
|
+
lanes?: Lane[]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface LaneView {
|
|
17
|
+
laneKey: number
|
|
18
|
+
schedule: Schedule
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export {
|
|
22
|
+
laneStartsIn,
|
|
23
|
+
currentTaskIndexForLane,
|
|
24
|
+
laneTaskComplete,
|
|
25
|
+
laneTaskInProgress,
|
|
26
|
+
laneCompletesAt,
|
|
27
|
+
currentTaskProgressFloatForLane,
|
|
28
|
+
} from './lane-core'
|
|
29
|
+
|
|
30
|
+
export function getLanes(entity: ScheduleData): LaneView[] {
|
|
31
|
+
const lanes = entity.lanes
|
|
32
|
+
if (!lanes || lanes.length === 0) return []
|
|
33
|
+
return lanes.map((l) => ({laneKey: l.lane_key.toNumber(), schedule: l.schedule}))
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function getLane(entity: ScheduleData, laneKey: number): LaneView | undefined {
|
|
37
|
+
const lanes = entity.lanes
|
|
38
|
+
if (!lanes) return undefined
|
|
39
|
+
for (const l of lanes) {
|
|
40
|
+
if (l.lane_key.toNumber() === laneKey) return {laneKey, schedule: l.schedule}
|
|
41
|
+
}
|
|
42
|
+
return undefined
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function mobilityLane(entity: ScheduleData): LaneView | undefined {
|
|
46
|
+
return getLane(entity, LANE_MOBILITY)
|
|
29
47
|
}
|
|
30
48
|
|
|
31
49
|
export function hasSchedule(entity: ScheduleData): boolean {
|
|
32
|
-
|
|
50
|
+
const lanes = entity.lanes
|
|
51
|
+
if (!lanes) return false
|
|
52
|
+
return lanes.some((l) => l.schedule.tasks.length > 0)
|
|
33
53
|
}
|
|
34
54
|
|
|
35
55
|
export function isIdle(entity: ScheduleData): boolean {
|
|
36
56
|
return !hasSchedule(entity)
|
|
37
57
|
}
|
|
38
58
|
|
|
59
|
+
export function isEntityIdle(entity: ScheduleData, now: Date): boolean {
|
|
60
|
+
const lanes = entity.lanes
|
|
61
|
+
if (!lanes) return true
|
|
62
|
+
return lanes.every((l) => core.currentTaskIndexForLane(l.schedule, now) < 0)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function entityIdleAt(entity: ScheduleData, _now: Date): Date | undefined {
|
|
66
|
+
const lanes = entity.lanes
|
|
67
|
+
if (!lanes) return undefined
|
|
68
|
+
let maxMs: number | undefined
|
|
69
|
+
for (const l of lanes) {
|
|
70
|
+
if (l.schedule.tasks.length === 0) continue
|
|
71
|
+
const endMs = l.schedule.started.toDate().getTime() + core.laneDuration(l.schedule) * 1000
|
|
72
|
+
if (maxMs === undefined || endMs > maxMs) maxMs = endMs
|
|
73
|
+
}
|
|
74
|
+
return maxMs === undefined ? undefined : new Date(maxMs)
|
|
75
|
+
}
|
|
76
|
+
|
|
39
77
|
export function getTasks(entity: ScheduleData): Task[] {
|
|
40
|
-
|
|
78
|
+
const lanes = entity.lanes
|
|
79
|
+
if (!lanes) return []
|
|
80
|
+
return lanes.flatMap((l) => l.schedule.tasks)
|
|
41
81
|
}
|
|
42
82
|
|
|
43
83
|
export function scheduleDuration(entity: ScheduleData): number {
|
|
44
|
-
|
|
45
|
-
|
|
84
|
+
let max = 0
|
|
85
|
+
for (const l of entity.lanes ?? []) max = Math.max(max, core.laneDuration(l.schedule))
|
|
86
|
+
return max
|
|
46
87
|
}
|
|
47
88
|
|
|
48
89
|
export function scheduleElapsed(entity: ScheduleData, now: Date): number {
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
return Math.max(0, elapsed)
|
|
90
|
+
let max = 0
|
|
91
|
+
for (const l of entity.lanes ?? []) max = Math.max(max, core.laneElapsed(l.schedule, now))
|
|
92
|
+
return max
|
|
53
93
|
}
|
|
54
94
|
|
|
55
95
|
export function scheduleRemaining(entity: ScheduleData, now: Date): number {
|
|
56
|
-
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
96
|
+
let remaining = 0
|
|
97
|
+
for (const l of entity.lanes ?? []) {
|
|
98
|
+
remaining = Math.max(remaining, core.laneRemaining(l.schedule, now))
|
|
99
|
+
}
|
|
100
|
+
return remaining
|
|
60
101
|
}
|
|
61
102
|
|
|
62
103
|
export function scheduleComplete(entity: ScheduleData, now: Date): boolean {
|
|
63
|
-
|
|
64
|
-
if (
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
for (let i = 0; i < entity.schedule.tasks.length; i++) {
|
|
76
|
-
const taskDuration = entity.schedule.tasks[i].duration.toNumber()
|
|
77
|
-
if (elapsed < timeAccum + taskDuration) {
|
|
78
|
-
return i
|
|
104
|
+
const lanes = entity.lanes
|
|
105
|
+
if (!lanes) return false
|
|
106
|
+
let hasAnyTask = false
|
|
107
|
+
let remaining = 0
|
|
108
|
+
for (const l of lanes) {
|
|
109
|
+
const tasks = l.schedule.tasks
|
|
110
|
+
if (tasks.length > 0) {
|
|
111
|
+
hasAnyTask = true
|
|
112
|
+
for (const t of tasks) {
|
|
113
|
+
if (t.type.toNumber() === TaskType.RESERVED) return false
|
|
114
|
+
}
|
|
79
115
|
}
|
|
80
|
-
|
|
116
|
+
remaining = Math.max(remaining, core.laneRemaining(l.schedule, now))
|
|
81
117
|
}
|
|
118
|
+
if (!hasAnyTask) return false
|
|
119
|
+
return remaining === 0
|
|
120
|
+
}
|
|
82
121
|
|
|
83
|
-
|
|
122
|
+
// Mirrors contract lane_front_complete: any lane whose front task is complete and non-reserved.
|
|
123
|
+
export function hasResolvable(entity: ScheduleData, now: Date): boolean {
|
|
124
|
+
for (const l of entity.lanes ?? []) {
|
|
125
|
+
if (core.laneTaskComplete(l.schedule, 0, now)) return true
|
|
126
|
+
}
|
|
127
|
+
return false
|
|
84
128
|
}
|
|
85
129
|
|
|
86
|
-
export function
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
130
|
+
export function currentTaskForLane(
|
|
131
|
+
entity: ScheduleData,
|
|
132
|
+
laneKey: number,
|
|
133
|
+
now: Date
|
|
134
|
+
): Task | undefined {
|
|
135
|
+
const lane = getLane(entity, laneKey)
|
|
136
|
+
return lane ? core.currentTask(lane.schedule, now) : undefined
|
|
90
137
|
}
|
|
91
138
|
|
|
92
|
-
export function
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
139
|
+
export function currentTaskTypeForLane(
|
|
140
|
+
entity: ScheduleData,
|
|
141
|
+
laneKey: number,
|
|
142
|
+
now: Date
|
|
143
|
+
): TaskType | undefined {
|
|
144
|
+
const lane = getLane(entity, laneKey)
|
|
145
|
+
return lane ? core.currentTaskType(lane.schedule, now) : undefined
|
|
96
146
|
}
|
|
97
147
|
|
|
98
|
-
export function
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
148
|
+
export function activeTasks(entity: ScheduleData, now: Date): Task[] {
|
|
149
|
+
const out: Task[] = []
|
|
150
|
+
for (const l of entity.lanes ?? []) {
|
|
151
|
+
const idx = core.currentTaskIndexForLane(l.schedule, now)
|
|
152
|
+
if (idx >= 0) out.push(l.schedule.tasks[idx])
|
|
103
153
|
}
|
|
104
|
-
return
|
|
154
|
+
return out
|
|
105
155
|
}
|
|
106
156
|
|
|
107
|
-
export
|
|
108
|
-
|
|
157
|
+
export interface ResolvedEvent {
|
|
158
|
+
laneKey: number
|
|
159
|
+
taskIndex: number
|
|
160
|
+
task: Task
|
|
161
|
+
completesAt: Date
|
|
162
|
+
}
|
|
109
163
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
164
|
+
// Canonical lane-front order (mirrors contract front_precedes): completion, then RECHARGE-last, then lane key.
|
|
165
|
+
function frontPrecedes(
|
|
166
|
+
a: {completesAt: Date; task: Task; laneKey: number},
|
|
167
|
+
b: {completesAt: Date; task: Task; laneKey: number}
|
|
168
|
+
): number {
|
|
169
|
+
if (a.completesAt.getTime() !== b.completesAt.getTime()) {
|
|
170
|
+
return a.completesAt.getTime() - b.completesAt.getTime()
|
|
171
|
+
}
|
|
172
|
+
const aRecharge = a.task.type.toNumber() === TaskType.RECHARGE
|
|
173
|
+
const bRecharge = b.task.type.toNumber() === TaskType.RECHARGE
|
|
174
|
+
if (aRecharge !== bRecharge) return aRecharge ? 1 : -1
|
|
175
|
+
return a.laneKey - b.laneKey
|
|
176
|
+
}
|
|
113
177
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
178
|
+
// Completed lane-fronts in canonical order (mirrors contract front_precedes).
|
|
179
|
+
export function resolveOrder(entity: ScheduleData, now: Date): ResolvedEvent[] {
|
|
180
|
+
const events: ResolvedEvent[] = []
|
|
181
|
+
for (const l of entity.lanes ?? []) {
|
|
182
|
+
const laneKey = l.lane_key.toNumber()
|
|
183
|
+
const startedMs = l.schedule.started.toDate().getTime()
|
|
184
|
+
let endSec = 0
|
|
185
|
+
for (let i = 0; i < l.schedule.tasks.length; i++) {
|
|
186
|
+
const task = l.schedule.tasks[i]
|
|
187
|
+
endSec += task.duration.toNumber()
|
|
188
|
+
const completesAt = new Date(startedMs + endSec * 1000)
|
|
189
|
+
if (task.type.toNumber() === TaskType.RESERVED) break
|
|
190
|
+
if (completesAt.getTime() > now.getTime()) break
|
|
191
|
+
events.push({laneKey, taskIndex: i, task, completesAt})
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
events.sort(frontPrecedes)
|
|
195
|
+
return events
|
|
117
196
|
}
|
|
118
197
|
|
|
119
|
-
export
|
|
120
|
-
|
|
198
|
+
export interface OrderedTask {
|
|
199
|
+
laneKey: number
|
|
200
|
+
taskIndex: number
|
|
201
|
+
task: Task
|
|
202
|
+
startsAt: Date
|
|
203
|
+
completesAt: Date
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Every task across all lanes in canonical order (mirrors contract front_precedes).
|
|
207
|
+
export function orderedTasks(entity: ScheduleData): OrderedTask[] {
|
|
208
|
+
const out: OrderedTask[] = []
|
|
209
|
+
for (const l of entity.lanes ?? []) {
|
|
210
|
+
const laneKey = l.lane_key.toNumber()
|
|
211
|
+
const startedMs = l.schedule.started.toDate().getTime()
|
|
212
|
+
let endSec = 0
|
|
213
|
+
for (let i = 0; i < l.schedule.tasks.length; i++) {
|
|
214
|
+
const task = l.schedule.tasks[i]
|
|
215
|
+
const startsAt = new Date(startedMs + endSec * 1000)
|
|
216
|
+
endSec += task.duration.toNumber()
|
|
217
|
+
const completesAt = new Date(startedMs + endSec * 1000)
|
|
218
|
+
out.push({laneKey, taskIndex: i, task, startsAt, completesAt})
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
out.sort(frontPrecedes)
|
|
222
|
+
return out
|
|
223
|
+
}
|
|
121
224
|
|
|
122
|
-
|
|
123
|
-
const
|
|
124
|
-
return
|
|
225
|
+
export function laneRemainingOf(entity: ScheduleData, laneKey: number, now: Date): number {
|
|
226
|
+
const lane = getLane(entity, laneKey)
|
|
227
|
+
return lane ? core.laneRemaining(lane.schedule, now) : 0
|
|
125
228
|
}
|
|
126
229
|
|
|
127
|
-
export function
|
|
128
|
-
|
|
230
|
+
export function laneStartsInOf(entity: ScheduleData, laneKey: number, now: Date): number {
|
|
231
|
+
const lane = getLane(entity, laneKey)
|
|
232
|
+
return lane ? core.laneStartsIn(lane.schedule, now) : 0
|
|
233
|
+
}
|
|
129
234
|
|
|
130
|
-
|
|
235
|
+
export function laneCompleteOf(entity: ScheduleData, laneKey: number, now: Date): boolean {
|
|
236
|
+
const lane = getLane(entity, laneKey)
|
|
237
|
+
return lane ? core.laneComplete(lane.schedule, now) : false
|
|
238
|
+
}
|
|
131
239
|
|
|
132
|
-
|
|
133
|
-
const
|
|
134
|
-
return
|
|
240
|
+
export function laneProgressOf(entity: ScheduleData, laneKey: number, now: Date): number {
|
|
241
|
+
const lane = getLane(entity, laneKey)
|
|
242
|
+
return lane ? core.laneProgress(lane.schedule, now) : 0
|
|
135
243
|
}
|
|
136
244
|
|
|
137
|
-
export function
|
|
138
|
-
|
|
245
|
+
export function laneTaskElapsedOf(
|
|
246
|
+
entity: ScheduleData,
|
|
247
|
+
laneKey: number,
|
|
248
|
+
index: number,
|
|
249
|
+
now: Date
|
|
250
|
+
): number {
|
|
251
|
+
const lane = getLane(entity, laneKey)
|
|
252
|
+
return lane ? core.laneTaskElapsed(lane.schedule, index, now) : 0
|
|
253
|
+
}
|
|
139
254
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
255
|
+
export function laneTaskRemainingOf(
|
|
256
|
+
entity: ScheduleData,
|
|
257
|
+
laneKey: number,
|
|
258
|
+
index: number,
|
|
259
|
+
now: Date
|
|
260
|
+
): number {
|
|
261
|
+
const lane = getLane(entity, laneKey)
|
|
262
|
+
return lane ? core.laneTaskRemaining(lane.schedule, index, now) : 0
|
|
143
263
|
}
|
|
144
264
|
|
|
145
|
-
export function
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
return
|
|
265
|
+
export function laneTaskCompleteOf(
|
|
266
|
+
entity: ScheduleData,
|
|
267
|
+
laneKey: number,
|
|
268
|
+
index: number,
|
|
269
|
+
now: Date
|
|
270
|
+
): boolean {
|
|
271
|
+
const lane = getLane(entity, laneKey)
|
|
272
|
+
return lane ? core.laneTaskComplete(lane.schedule, index, now) : false
|
|
153
273
|
}
|
|
154
274
|
|
|
155
|
-
export function
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
const taskStartMs = startedMs + getTaskStartTime(entity, index) * 1000
|
|
164
|
-
const elapsedMs = now.getTime() - taskStartMs
|
|
165
|
-
if (elapsedMs <= 0) return 0
|
|
166
|
-
return Math.min(1, elapsedMs / durationMs)
|
|
275
|
+
export function laneTaskInProgressOf(
|
|
276
|
+
entity: ScheduleData,
|
|
277
|
+
laneKey: number,
|
|
278
|
+
index: number,
|
|
279
|
+
now: Date
|
|
280
|
+
): boolean {
|
|
281
|
+
const lane = getLane(entity, laneKey)
|
|
282
|
+
return lane ? core.laneTaskInProgress(lane.schedule, index, now) : false
|
|
167
283
|
}
|
|
168
284
|
|
|
169
|
-
export function
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
const elapsed = scheduleElapsed(entity, now)
|
|
173
|
-
return Math.min(1, elapsed / duration)
|
|
285
|
+
export function currentTaskIndexOf(entity: ScheduleData, laneKey: number, now: Date): number {
|
|
286
|
+
const lane = getLane(entity, laneKey)
|
|
287
|
+
return lane ? core.currentTaskIndexForLane(lane.schedule, now) : -1
|
|
174
288
|
}
|
|
175
289
|
|
|
176
|
-
|
|
177
|
-
return
|
|
290
|
+
function entityDoesTaskType(entity: ScheduleData, taskType: TaskType, now: Date): boolean {
|
|
291
|
+
return activeTasks(entity, now).some((t) => t.type.toNumber() === taskType)
|
|
178
292
|
}
|
|
179
293
|
|
|
180
294
|
export function isInFlight(entity: ScheduleData, now: Date): boolean {
|
|
181
|
-
|
|
295
|
+
const lane = mobilityLane(entity)
|
|
296
|
+
return lane ? core.currentTaskType(lane.schedule, now) === TaskType.TRAVEL : false
|
|
182
297
|
}
|
|
183
298
|
|
|
184
299
|
export function isRecharging(entity: ScheduleData, now: Date): boolean {
|
|
185
|
-
return
|
|
300
|
+
return entityDoesTaskType(entity, TaskType.RECHARGE, now)
|
|
186
301
|
}
|
|
187
302
|
|
|
188
303
|
export function isLoading(entity: ScheduleData, now: Date): boolean {
|
|
189
|
-
return
|
|
304
|
+
return entityDoesTaskType(entity, TaskType.LOAD, now)
|
|
190
305
|
}
|
|
191
306
|
|
|
192
307
|
export function isUnloading(entity: ScheduleData, now: Date): boolean {
|
|
193
|
-
return
|
|
308
|
+
return entityDoesTaskType(entity, TaskType.UNLOAD, now)
|
|
194
309
|
}
|
|
195
310
|
|
|
196
311
|
export function isGathering(entity: ScheduleData, now: Date): boolean {
|
|
197
|
-
return
|
|
312
|
+
return entityDoesTaskType(entity, TaskType.GATHER, now)
|
|
198
313
|
}
|