Files
TheTree_mock/scripts/inventory/logistics_request.gd
T

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