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
121 lines
4.7 KiB
// ─────────────────────────────────────────────────────────────────────────────
|
|
// 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
|
|
} |