///|
/// Creates an empty multi-resource lease table.
pub fn LeaseTable::new() -> LeaseTable {
{ states: [], events: [], accepted_commands: 0, rejected_commands: 0 }
}
///|
fn clone_states(states : Array[LeaseState]) -> Array[LeaseState] {
states.map(fn(state) { state })
}
///|
fn clone_events(events : Array[LeaseEvent]) -> Array[LeaseEvent] {
events.map(fn(event) { event })
}
///|
/// Returns the state for a resource.
pub fn LeaseTable::get(self : LeaseTable, resource : String) -> LeaseState? {
for state in self.states {
if state.resource == resource {
return Some(state)
}
}
None
}
///|
/// Returns all leases active at an explicit logical time.
pub fn LeaseTable::active_leases(self : LeaseTable, now : Int) -> Array[Lease] {
let result : Array[Lease] = []
for state in self.states {
match state.active_lease(now) {
Some(lease) => result.push(lease)
None => ()
}
}
result
}
///|
/// Applies one resource command and records accepted transitions.
pub fn LeaseTable::apply(
self : LeaseTable,
resource : String,
command : LeaseCommand,
now : Int,
) -> TableDecision {
let states = clone_states(self.states)
let events = clone_events(self.events)
let mut target = LeaseState::new(resource)
let mut found = false
let mut target_index = -1
for index, state in states {
if state.resource == resource {
target = state
found = true
target_index = index
break
}
}
let decision = target.apply(command, now)
if decision.accepted {
if found {
states[target_index] = decision.state
} else {
states.push(decision.state)
}
match decision.event {
Some(event) => events.push(event)
None => ()
}
}
let accepted_delta = if decision.accepted { 1 } else { 0 }
let rejected_delta = if decision.accepted { 0 } else { 1 }
let table : LeaseTable = {
states,
events,
accepted_commands: self.accepted_commands + accepted_delta,
rejected_commands: self.rejected_commands + rejected_delta,
}
{ table, decision }
}
///|
/// Applies commands in input order and preserves every decision.
pub fn LeaseTable::apply_batch(
self : LeaseTable,
commands : Array[TimedCommand],
) -> BatchResult {
let mut table = self
let decisions : Array[LeaseDecision] = []
let mut accepted_count = 0
let mut rejected_count = 0
for command in commands {
let result = table.apply(command.resource, command.command, command.at)
table = result.table
decisions.push(result.decision)
if result.decision.accepted {
accepted_count = accepted_count + 1
} else {
rejected_count = rejected_count + 1
}
}
{ table, decisions, accepted: accepted_count, rejected: rejected_count }
}
///|
/// Reaps every expired stored lease in deterministic table order.
pub fn LeaseTable::reap_expired(self : LeaseTable, now : Int) -> BatchResult {
let commands : Array[TimedCommand] = []
for state in self.states {
match state.lease {
Some(lease) =>
if !lease.is_active(now) {
commands.push({ resource: state.resource, command: Reap, at: now })
}
None => ()
}
}
self.apply_batch(commands)
}