You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

121 lines
4.7 KiB

7 months ago
// ─────────────────────────────────────────────────────────────────────────────
// VICE · Session
// ─────────────────────────────────────────────────────────────────────────────
import type {OptionID, EntityPathKey} from './types.ts'
import type {GacGraph, GacState} from './gac.ts'
import type {SessionState, PropagationResult, SelectResult, DeselectResult} from './state_types.ts'
import type {ViceSession} from './engine_types.ts'
import {GacEngine} from './gac.ts'
export class Session implements ViceSession {
private state: GacState
constructor(
private readonly graph: GacGraph,
private readonly engine: GacEngine,
) {
this.state = engine.initState(graph)
this.state = engine.propagate(graph, this.state).state
}
select(path: EntityPathKey, optionId: OptionID, quantity?: number): SelectResult {
const vs = this.state.variables.get(path)
if (!vs) return {ok: false, reason: 'INVALID_PATH'}
if (!vs.active) return {ok: false, reason: 'INACTIVE_ATTRIBUTE'}
if (!vs.domain.has(optionId)) return {ok: false, reason: 'OPTION_NOT_IN_DOMAIN'}
const prev = this.state
const next = this.engine.applySelection(this.state, path, optionId, quantity)
const result = this.engine.propagate(this.graph, next)
this.state = result.state
return {ok: true, result: this.toPropagationResult(result.state, prev)}
}
deselect(path: EntityPathKey): DeselectResult {
const vs = this.state.variables.get(path)
if (!vs) return {ok: false, reason: 'INVALID_PATH'}
if (!vs.active) return {ok: false, reason: 'INACTIVE_ATTRIBUTE'}
const prev = this.state
const next = this.engine.applyDeselection(this.state, path)
const result = this.engine.propagate(this.graph, next)
this.state = result.state
return {ok: true, result: this.toPropagationResult(result.state, prev)}
}
getState(): SessionState {
return this.toSessionState(this.state)
}
reset(): SessionState {
this.state = this.engine.initState(this.graph)
this.state = this.engine.propagate(this.graph, this.state).state
return this.toSessionState(this.state)
}
private toSessionState(state: GacState): SessionState {
const attributes = new Map<EntityPathKey, import('./state_types.ts').AttributeState>()
const groups = new Map<EntityPathKey, import('./state_types.ts').GroupState>()
for (const [path, vs] of state.variables) {
const variable = this.graph.variables.get(path)
if (!variable) continue
attributes.set(path, {
path,
attrId: variable.attrId,
active: vs.active,
domain: vs.domain,
current: vs.current,
violations: vs.violations.map(v => ({
constraintId: v.constraintId,
optionId: v.optionId,
reason: v.reason,
severity: v.severity,
})),
})
}
for (const [path, gs] of state.groups) {
const group = this.graph.groups.get(path)
if (!group) continue
groups.set(path, {path, groupId: group.groupId, active: gs.active})
}
return {entityId: this.graph.entityId, attributes, groups, consistent: state.consistent}
}
private toPropagationResult(next: GacState, prev: GacState): PropagationResult {
const changed = new Set<EntityPathKey>()
for (const [path, vs] of next.variables) {
const prevVs = prev.variables.get(path)
if (!prevVs) {
changed.add(path);
continue
}
if (
vs.active !== prevVs.active ||
vs.current?.optionId !== prevVs.current?.optionId ||
!setsEqual(vs.domain, prevVs.domain)
) changed.add(path)
}
for (const [path, gs] of next.groups) {
const prevGs = prev.groups.get(path)
if (!prevGs || gs.active !== prevGs.active) changed.add(path)
}
return {state: this.toSessionState(next), consistent: next.consistent, changed}
}
}
function setsEqual<T>(a: Set<T>, b: Set<T>): boolean {
if (a.size !== b.size) return false
for (const v of a) if (!b.has(v)) return false
return true
}

Powered by TurnKey Linux.