// ───────────────────────────────────────────────────────────────────────────── // 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() const groups = new Map() 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() 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(a: Set, b: Set): boolean { if (a.size !== b.size) return false for (const v of a) if (!b.has(v)) return false return true }