223 lines
6.8 KiB
GDScript
223 lines
6.8 KiB
GDScript
class_name LogisticsRequest
|
|
extends RefCounted
|
|
|
|
enum State {
|
|
OPEN,
|
|
RESERVED,
|
|
ASSIGNED,
|
|
PICKING_UP,
|
|
DELIVERING,
|
|
COMPLETED,
|
|
BLOCKED,
|
|
CANCELLED,
|
|
}
|
|
|
|
const MAX_PRIORITY := 100
|
|
|
|
var request_id: StringName = &""
|
|
var source_inventory_id: StringName = &""
|
|
var destination_inventory_id: StringName = &""
|
|
var item_requirements: Dictionary = {}
|
|
var priority := 0
|
|
var created_at := 0
|
|
var deadline := -1
|
|
var total_weight := 0
|
|
var reserved_stack_ids: Array[StringName] = []
|
|
var reservation_ids: Array[StringName] = []
|
|
var assigned_carrier_id: StringName = &""
|
|
var state: State = State.OPEN
|
|
var retry_count := 0
|
|
var state_reason := ""
|
|
var state_changed_at := 0
|
|
|
|
|
|
func configure(
|
|
p_request_id: StringName,
|
|
p_source_inventory_id: StringName,
|
|
p_destination_inventory_id: StringName,
|
|
p_item_requirements: Dictionary,
|
|
p_priority: int,
|
|
p_created_at: int,
|
|
p_deadline: int,
|
|
p_total_weight: int
|
|
) -> LogisticsRequest:
|
|
request_id = p_request_id
|
|
source_inventory_id = p_source_inventory_id
|
|
destination_inventory_id = p_destination_inventory_id
|
|
item_requirements = _normalize_requirements(p_item_requirements)
|
|
priority = clampi(p_priority, 0, MAX_PRIORITY)
|
|
created_at = maxi(p_created_at, 0)
|
|
deadline = p_deadline
|
|
total_weight = maxi(p_total_weight, 0)
|
|
state_changed_at = created_at
|
|
return self
|
|
|
|
|
|
func mark_reserved(
|
|
p_reservation_ids: Array[StringName],
|
|
p_reserved_stack_ids: Array[StringName] = [],
|
|
absolute_minute: int = -1
|
|
) -> bool:
|
|
if state != State.OPEN and state != State.BLOCKED:
|
|
return false
|
|
if p_reservation_ids.is_empty():
|
|
return false
|
|
reservation_ids.assign(p_reservation_ids)
|
|
reserved_stack_ids.assign(p_reserved_stack_ids)
|
|
assigned_carrier_id = &""
|
|
return transition_to(State.RESERVED, "", absolute_minute)
|
|
|
|
|
|
func assign(carrier_id: StringName, absolute_minute: int = -1) -> bool:
|
|
if carrier_id == &"" or state != State.RESERVED:
|
|
return false
|
|
assigned_carrier_id = carrier_id
|
|
if transition_to(State.ASSIGNED, "", absolute_minute):
|
|
return true
|
|
assigned_carrier_id = &""
|
|
return false
|
|
|
|
|
|
func unassign(absolute_minute: int = -1) -> bool:
|
|
if state != State.ASSIGNED:
|
|
return false
|
|
assigned_carrier_id = &""
|
|
return transition_to(State.RESERVED, "", absolute_minute)
|
|
|
|
|
|
func mark_blocked(reason: String, absolute_minute: int = -1) -> bool:
|
|
if state == State.COMPLETED or state == State.CANCELLED:
|
|
return false
|
|
retry_count += 1
|
|
assigned_carrier_id = &""
|
|
return transition_to(State.BLOCKED, reason, absolute_minute)
|
|
|
|
|
|
func cancel(reason: String = "", absolute_minute: int = -1) -> bool:
|
|
return transition_to(State.CANCELLED, reason, absolute_minute)
|
|
|
|
|
|
func transition_to(next_state: State, reason: String = "", absolute_minute: int = -1) -> bool:
|
|
if state == next_state:
|
|
return true
|
|
if not _can_transition(state, next_state):
|
|
return false
|
|
state = next_state
|
|
state_reason = reason
|
|
state_changed_at = created_at if absolute_minute < 0 else absolute_minute
|
|
if state == State.COMPLETED or state == State.CANCELLED:
|
|
assigned_carrier_id = &""
|
|
return true
|
|
|
|
|
|
func is_terminal() -> bool:
|
|
return state == State.COMPLETED or state == State.CANCELLED
|
|
|
|
|
|
func is_dispatchable() -> bool:
|
|
return state == State.RESERVED and assigned_carrier_id == &"" and total_weight > 0
|
|
|
|
|
|
func create_snapshot() -> Dictionary:
|
|
return {
|
|
&"request_id": String(request_id),
|
|
&"source_inventory_id": String(source_inventory_id),
|
|
&"destination_inventory_id": String(destination_inventory_id),
|
|
&"item_requirements": item_requirements.duplicate(true),
|
|
&"priority": priority,
|
|
&"created_at": created_at,
|
|
&"deadline": deadline,
|
|
&"total_weight": total_weight,
|
|
&"reserved_stack_ids": _names_to_strings(reserved_stack_ids),
|
|
&"reservation_ids": _names_to_strings(reservation_ids),
|
|
&"assigned_carrier_id": String(assigned_carrier_id),
|
|
&"state": State.keys()[state],
|
|
&"retry_count": retry_count,
|
|
&"state_reason": state_reason,
|
|
&"state_changed_at": state_changed_at,
|
|
}
|
|
|
|
|
|
static func from_snapshot(snapshot: Dictionary) -> LogisticsRequest:
|
|
if not _is_valid_snapshot(snapshot):
|
|
return null
|
|
var state_index := State.keys().find(String(snapshot.get(&"state", "OPEN")))
|
|
if state_index < 0:
|
|
return null
|
|
var request := LogisticsRequest.new().configure(
|
|
StringName(snapshot.get(&"request_id", "")),
|
|
StringName(snapshot.get(&"source_inventory_id", "")),
|
|
StringName(snapshot.get(&"destination_inventory_id", "")),
|
|
snapshot.get(&"item_requirements", {}),
|
|
int(snapshot.get(&"priority", 0)),
|
|
int(snapshot.get(&"created_at", 0)),
|
|
int(snapshot.get(&"deadline", -1)),
|
|
int(snapshot.get(&"total_weight", 0))
|
|
)
|
|
request.reserved_stack_ids = _parse_names(snapshot.get(&"reserved_stack_ids", []))
|
|
request.reservation_ids = _parse_names(snapshot.get(&"reservation_ids", []))
|
|
request.assigned_carrier_id = StringName(snapshot.get(&"assigned_carrier_id", ""))
|
|
request.state = state_index as State
|
|
request.retry_count = maxi(int(snapshot.get(&"retry_count", 0)), 0)
|
|
request.state_reason = String(snapshot.get(&"state_reason", ""))
|
|
request.state_changed_at = int(snapshot.get(&"state_changed_at", request.created_at))
|
|
if request.state == State.ASSIGNED and request.assigned_carrier_id == &"":
|
|
return null
|
|
return request
|
|
|
|
|
|
static func _can_transition(from_state: State, to_state: State) -> bool:
|
|
match from_state:
|
|
State.OPEN:
|
|
return to_state in [State.RESERVED, State.BLOCKED, State.CANCELLED]
|
|
State.RESERVED:
|
|
return to_state in [State.OPEN, State.ASSIGNED, State.BLOCKED, State.CANCELLED]
|
|
State.ASSIGNED:
|
|
return to_state in [State.RESERVED, State.PICKING_UP, State.BLOCKED, State.CANCELLED]
|
|
State.PICKING_UP:
|
|
return to_state in [State.DELIVERING, State.BLOCKED, State.CANCELLED]
|
|
State.DELIVERING:
|
|
return to_state in [State.COMPLETED, State.BLOCKED, State.CANCELLED]
|
|
State.BLOCKED:
|
|
return to_state in [State.OPEN, State.RESERVED, State.CANCELLED]
|
|
_:
|
|
return false
|
|
|
|
|
|
static func _normalize_requirements(source: Variant) -> Dictionary:
|
|
var normalized: Dictionary = {}
|
|
if not source is Dictionary:
|
|
return normalized
|
|
for raw_item_id: Variant in source:
|
|
var item_id := StringName(raw_item_id)
|
|
var quantity := int(source[raw_item_id])
|
|
if item_id != &"" and quantity > 0:
|
|
normalized[item_id] = quantity
|
|
return normalized
|
|
|
|
|
|
static func _is_valid_snapshot(snapshot: Dictionary) -> bool:
|
|
return (
|
|
not String(snapshot.get(&"request_id", "")).is_empty()
|
|
and not String(snapshot.get(&"source_inventory_id", "")).is_empty()
|
|
and not String(snapshot.get(&"destination_inventory_id", "")).is_empty()
|
|
and snapshot.get(&"item_requirements", null) is Dictionary
|
|
and int(snapshot.get(&"total_weight", -1)) >= 0
|
|
)
|
|
|
|
|
|
static func _parse_names(source: Variant) -> Array[StringName]:
|
|
var result: Array[StringName] = []
|
|
if not source is Array:
|
|
return result
|
|
for value: Variant in source:
|
|
result.append(StringName(value))
|
|
return result
|
|
|
|
|
|
static func _names_to_strings(source: Array[StringName]) -> Array[String]:
|
|
var result: Array[String] = []
|
|
for value: StringName in source:
|
|
result.append(String(value))
|
|
return result
|