@shipload/sdk 1.0.0-next.31 → 1.0.0-next.32

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.
@@ -5,7 +5,7 @@ import {PlotManager} from './plot'
5
5
  import {getItem} from '../data/catalog'
6
6
  import {calc_craft_duration} from '../capabilities/crafting'
7
7
  import {getLanes, getTasks} from '../scheduling/schedule'
8
- import {TaskType} from '../types'
8
+ import {HoldKind, TaskType} from '../types'
9
9
  import type {
10
10
  BuildableTarget,
11
11
  FinalizerEntityRef,
@@ -102,7 +102,7 @@ export class ConstructionManager extends BaseManager {
102
102
  let cumulativeSec = 0
103
103
  for (const task of lane.schedule.tasks) {
104
104
  cumulativeSec += task.duration.toNumber()
105
- if (!isTransferTask(task)) continue
105
+ if (!isPushTask(task)) continue
106
106
  if (!task.entitytarget) continue
107
107
  const projectedEndMs = startedMs + cumulativeSec * 1000
108
108
  if (projectedEndMs < nowMs) continue
@@ -145,51 +145,53 @@ export class ConstructionManager extends BaseManager {
145
145
 
146
146
  private plotReservation(
147
147
  plot: ServerContract.Types.entity_info,
148
+ builder: ServerContract.Types.entity_info | undefined,
148
149
  now: Date
149
150
  ): {
150
151
  builderId: UInt64
151
- group?: UInt64
152
152
  startsAt: number
153
153
  completesAt: number
154
154
  hasStarted: boolean
155
155
  } | null {
156
- for (const lane of getLanes(plot)) {
156
+ const hold = plot.holds.find((h) => h.kind.toNumber() === HoldKind.BUILD)
157
+ if (!hold) return null
158
+ const builderId = hold.counterpart.entity_id
159
+ const completesAt = hold.until.toDate().getTime()
160
+ const startsAt = this.builderBuildStart(builder, plot.id) ?? completesAt
161
+ return {
162
+ builderId,
163
+ startsAt,
164
+ completesAt,
165
+ hasStarted: startsAt <= now.getTime(),
166
+ }
167
+ }
168
+
169
+ private builderBuildStart(
170
+ builder: ServerContract.Types.entity_info | undefined,
171
+ plotId: UInt64
172
+ ): number | undefined {
173
+ if (!builder) return undefined
174
+ for (const lane of getLanes(builder)) {
157
175
  const startedMs = lane.schedule.started.toDate().getTime()
158
176
  let startSec = 0
159
177
  for (const task of lane.schedule.tasks) {
160
- if (task.type.toNumber() === TaskType.RESERVED) {
161
- if (!task.entitytarget) return null
162
- const startsAt = startedMs + startSec * 1000
163
- const completesAt = startsAt + task.duration.toNumber() * 1000
164
- return {
165
- builderId: task.entitytarget.entity_id,
166
- group: task.entitygroup ?? undefined,
167
- startsAt,
168
- completesAt,
169
- hasStarted: startsAt <= now.getTime(),
170
- }
171
- }
178
+ if (isBuildOfPlot(task, plotId)) return startedMs + startSec * 1000
172
179
  startSec += task.duration.toNumber()
173
180
  }
174
181
  }
175
- return null
182
+ return undefined
176
183
  }
177
184
 
178
185
  private builderCancelability(
179
186
  builder: ServerContract.Types.entity_info | undefined,
180
- group: UInt64 | undefined
187
+ plotId: UInt64
181
188
  ): {cancelable: boolean; blockingTaskCount: number} {
182
- if (!builder || group === undefined) {
189
+ if (!builder) {
183
190
  return {cancelable: false, blockingTaskCount: 0}
184
191
  }
185
192
  for (const lane of getLanes(builder)) {
186
193
  const tasks = lane.schedule.tasks
187
- const buildIdx = tasks.findIndex(
188
- (t) =>
189
- t.type.toNumber() === TaskType.BUILDPLOT &&
190
- t.entitygroup !== undefined &&
191
- t.entitygroup.equals(group)
192
- )
194
+ const buildIdx = tasks.findIndex((t) => isBuildOfPlot(t, plotId))
193
195
  if (buildIdx < 0) continue
194
196
  const trailing = tasks.length - 1 - buildIdx
195
197
  return {cancelable: trailing === 0, blockingTaskCount: trailing}
@@ -200,14 +202,14 @@ export class ConstructionManager extends BaseManager {
200
202
  private buildFromReservation(
201
203
  res: {
202
204
  builderId: UInt64
203
- group?: UInt64
204
205
  startsAt: number
205
206
  completesAt: number
206
207
  hasStarted: boolean
207
208
  },
209
+ plotId: UInt64,
208
210
  builder: ServerContract.Types.entity_info | undefined
209
211
  ): ScheduledBuild {
210
- const {cancelable, blockingTaskCount} = this.builderCancelability(builder, res.group)
212
+ const {cancelable, blockingTaskCount} = this.builderCancelability(builder, plotId)
211
213
  return {
212
214
  shipId: res.builderId,
213
215
  shipName: builder?.entity_name || res.builderId.toString(),
@@ -224,10 +226,12 @@ export class ConstructionManager extends BaseManager {
224
226
  entities: ServerContract.Types.entity_info[],
225
227
  now: Date
226
228
  ): ScheduledBuild | null {
227
- const res = this.plotReservation(plot, now)
229
+ const hold = plot.holds.find((h) => h.kind.toNumber() === HoldKind.BUILD)
230
+ if (!hold) return null
231
+ const builder = entities.find((e) => e.id.equals(hold.counterpart.entity_id))
232
+ const res = this.plotReservation(plot, builder, now)
228
233
  if (!res) return null
229
- const builder = entities.find((e) => e.id.equals(res.builderId))
230
- return this.buildFromReservation(res, builder)
234
+ return this.buildFromReservation(res, plot.id, builder)
231
235
  }
232
236
 
233
237
  scheduledBuildsByTarget(
@@ -238,10 +242,12 @@ export class ConstructionManager extends BaseManager {
238
242
  const out = new Map<string, ScheduledBuild>()
239
243
  for (const entity of entities) {
240
244
  if (entity.type.toString() !== 'plot') continue
241
- const res = this.plotReservation(entity, now)
245
+ const hold = entity.holds.find((h) => h.kind.toNumber() === HoldKind.BUILD)
246
+ if (!hold) continue
247
+ const builder = byId.get(hold.counterpart.entity_id.toString())
248
+ const res = this.plotReservation(entity, builder, now)
242
249
  if (!res) continue
243
- const builder = byId.get(res.builderId.toString())
244
- out.set(entity.id.toString(), this.buildFromReservation(res, builder))
250
+ out.set(entity.id.toString(), this.buildFromReservation(res, entity.id, builder))
245
251
  }
246
252
  return out
247
253
  }
@@ -354,15 +360,22 @@ function partitionSources(
354
360
  return {eligible, unreachable}
355
361
  }
356
362
 
357
- function isTransferTask(task: ServerContract.Types.task): boolean {
358
- const type = task.type.toNumber()
359
- return type === TaskType.LOAD || type === TaskType.UNLOAD
363
+ function isPushTask(task: ServerContract.Types.task): boolean {
364
+ return task.type.toNumber() === TaskType.UNLOAD
365
+ }
366
+
367
+ function isBuildOfPlot(task: ServerContract.Types.task, plotId: UInt64): boolean {
368
+ return (
369
+ task.type.toNumber() === TaskType.BUILDPLOT &&
370
+ task.entitytarget !== undefined &&
371
+ task.entitytarget.entity_id.equals(plotId)
372
+ )
360
373
  }
361
374
 
362
375
  function reservationsOf(source: ServerContract.Types.entity_info): Reservation[] {
363
376
  const out = new Map<string, Reservation>()
364
377
  for (const task of getTasks(source)) {
365
- if (!isTransferTask(task)) continue
378
+ if (!isPushTask(task)) continue
366
379
  if (!task.entitytarget) continue
367
380
  const targetType = task.entitytarget.entity_type
368
381
  const targetId = task.entitytarget.entity_id
@@ -20,12 +20,11 @@ export function energyAtTime(entity: Projectable, now: Date): number {
20
20
 
21
21
  for (const {task, startsAt} of ordered) {
22
22
  const duration = task.duration.toNumber()
23
- const isReserved = task.type.toNumber() === TaskType.RESERVED
24
23
  const elapsed = Math.min(
25
24
  Math.max(0, Math.floor((nowMs - startsAt.getTime()) / 1000)),
26
25
  duration
27
26
  )
28
- const complete = !isReserved && elapsed >= duration
27
+ const complete = elapsed >= duration
29
28
  const inProgress = !complete && elapsed > 0 && elapsed < duration
30
29
 
31
30
  if (!complete && !inProgress) continue
@@ -0,0 +1,44 @@
1
+ import type {Action, UInt64} from '@wharfkit/antelope'
2
+ import type {ServerContract} from '../contracts'
3
+ import type {ActionsManager} from '../managers/actions'
4
+ import {hasResolvable, type ScheduleData} from './schedule'
5
+
6
+ type EntityInfo = ServerContract.Types.entity_info
7
+
8
+ export type CounterpartLookup = (entityId: UInt64) => EntityInfo | undefined
9
+
10
+ export type IdleResolveTarget = ScheduleData & {id: UInt64}
11
+
12
+ // A hold's driving task lives on its counterpart, so a hold resolves the counterpart, never the blocker.
13
+ export function composeIdleResolve(
14
+ blocker: IdleResolveTarget,
15
+ action: Action,
16
+ actions: ActionsManager,
17
+ now: Date,
18
+ lookupCounterpart?: CounterpartLookup
19
+ ): Action[] {
20
+ const ids: UInt64[] = []
21
+ const seen = new Set<string>()
22
+
23
+ const add = (id: UInt64) => {
24
+ const key = id.toString()
25
+ if (seen.has(key)) return
26
+ seen.add(key)
27
+ ids.push(id)
28
+ }
29
+
30
+ if (hasResolvable(blocker, now)) {
31
+ add(blocker.id)
32
+ }
33
+
34
+ for (const hold of blocker.holds ?? []) {
35
+ const counterpartId = hold.counterpart.entity_id
36
+ if (lookupCounterpart) {
37
+ const counterpart = lookupCounterpart(counterpartId)
38
+ if (!counterpart || !hasResolvable(counterpart, now)) continue
39
+ }
40
+ add(counterpartId)
41
+ }
42
+
43
+ return [...ids.map((id) => actions.resolve(id)), action]
44
+ }
@@ -1,5 +1,5 @@
1
1
  import type {ServerContract} from '../contracts'
2
- import {TaskType} from '../types'
2
+ import type {TaskType} from '../types'
3
3
 
4
4
  type Schedule = ServerContract.Types.schedule
5
5
  type Task = ServerContract.Types.task
@@ -27,7 +27,6 @@ export function laneRemaining(schedule: Schedule, now: Date): number {
27
27
 
28
28
  export function laneComplete(schedule: Schedule, now: Date): boolean {
29
29
  if (schedule.tasks.length === 0) return false
30
- if (schedule.tasks.some((t) => t.type.toNumber() === TaskType.RESERVED)) return false
31
30
  return laneRemaining(schedule, now) === 0
32
31
  }
33
32
 
@@ -87,7 +86,6 @@ export function laneTaskRemaining(schedule: Schedule, index: number, now: Date):
87
86
 
88
87
  export function laneTaskComplete(schedule: Schedule, index: number, now: Date): boolean {
89
88
  if (index < 0 || index >= schedule.tasks.length) return false
90
- if (schedule.tasks[index].type.toNumber() === TaskType.RESERVED) return false
91
89
  const taskDuration = schedule.tasks[index].duration.toNumber()
92
90
  return laneTaskElapsed(schedule, index, now) >= taskDuration
93
91
  }
@@ -442,12 +442,11 @@ export function projectEntityAt(entity: Projectable, now: Date): ProjectedEntity
442
442
 
443
443
  for (const {task, startsAt} of ordered) {
444
444
  const duration = task.duration.toNumber()
445
- const isReserved = task.type.toNumber() === TaskType.RESERVED
446
445
  const elapsed = Math.min(
447
446
  Math.max(0, Math.floor((nowMs - startsAt.getTime()) / 1000)),
448
447
  duration
449
448
  )
450
- const taskComplete = !isReserved && elapsed >= duration
449
+ const taskComplete = elapsed >= duration
451
450
  const taskInProgress = elapsed > 0 && elapsed < duration
452
451
 
453
452
  if (!taskComplete && !taskInProgress) {
@@ -5,12 +5,14 @@ import * as core from './lane-core'
5
5
  type Schedule = ServerContract.Types.schedule
6
6
  type Task = ServerContract.Types.task
7
7
  type Lane = ServerContract.Types.lane
8
+ type Hold = ServerContract.Types.hold
8
9
 
9
10
  export const LANE_MOBILITY = 0
10
11
  export const LANE_BARRIER = 255
11
12
 
12
13
  export interface ScheduleData {
13
14
  lanes?: Lane[]
15
+ holds?: Hold[]
14
16
  }
15
17
 
16
18
  export interface LaneView {
@@ -52,11 +54,17 @@ export function hasSchedule(entity: ScheduleData): boolean {
52
54
  return lanes.some((l) => l.schedule.tasks.length > 0)
53
55
  }
54
56
 
57
+ export function hasHolds(entity: ScheduleData): boolean {
58
+ const holds = entity.holds
59
+ return !!holds && holds.length > 0
60
+ }
61
+
55
62
  export function isIdle(entity: ScheduleData): boolean {
56
- return !hasSchedule(entity)
63
+ return !hasSchedule(entity) && !hasHolds(entity)
57
64
  }
58
65
 
59
66
  export function isEntityIdle(entity: ScheduleData, now: Date): boolean {
67
+ if (hasHolds(entity)) return false
60
68
  const lanes = entity.lanes
61
69
  if (!lanes) return true
62
70
  return lanes.every((l) => core.currentTaskIndexForLane(l.schedule, now) < 0)
@@ -106,13 +114,7 @@ export function scheduleComplete(entity: ScheduleData, now: Date): boolean {
106
114
  let hasAnyTask = false
107
115
  let remaining = 0
108
116
  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
- }
115
- }
117
+ if (l.schedule.tasks.length > 0) hasAnyTask = true
116
118
  remaining = Math.max(remaining, core.laneRemaining(l.schedule, now))
117
119
  }
118
120
  if (!hasAnyTask) return false
@@ -186,7 +188,6 @@ export function resolveOrder(entity: ScheduleData, now: Date): ResolvedEvent[] {
186
188
  const task = l.schedule.tasks[i]
187
189
  endSec += task.duration.toNumber()
188
190
  const completesAt = new Date(startedMs + endSec * 1000)
189
- if (task.type.toNumber() === TaskType.RESERVED) break
190
191
  if (completesAt.getTime() > now.getTime()) break
191
192
  events.push({laneKey, taskIndex: i, task, completesAt})
192
193
  }
@@ -19,5 +19,7 @@ export function parseWireEntity(raw: WireEntity): ServerContract.Types.entity_in
19
19
  }
20
20
  delete shaped.name
21
21
 
22
+ if (shaped.holds === undefined) shaped.holds = []
23
+
22
24
  return ServerContract.Types.entity_info.from(shaped)
23
25
  }
package/src/types.ts CHANGED
@@ -56,7 +56,13 @@ export enum TaskType {
56
56
  DEMOLISH = 13,
57
57
  CLAIMPLOT = 14,
58
58
  BUILDPLOT = 15,
59
- RESERVED = 16,
59
+ }
60
+
61
+ export enum HoldKind {
62
+ PULL = 1,
63
+ PUSH = 2,
64
+ GATHER = 3,
65
+ BUILD = 4,
60
66
  }
61
67
 
62
68
  export enum LocationType {