hard-won battle to fix view resolution
This commit is contained in:
parent
bdc6958b49
commit
29b1b8bb9a
@ -1,5 +1,4 @@
|
|||||||
import { PropertyID, PropertyTypes } from "@src/core/types";
|
import { PropertyID, PropertyTypes } from "@src/core/types";
|
||||||
import { CollapsedDelta } from "@src/views/lossless";
|
|
||||||
import { ResolverPlugin, DependencyStates } from "../plugin";
|
import { ResolverPlugin, DependencyStates } from "../plugin";
|
||||||
|
|
||||||
type MaxPluginState = {
|
type MaxPluginState = {
|
||||||
@ -29,7 +28,6 @@ export class MaxPlugin<Target extends PropertyID> extends ResolverPlugin<MaxPlug
|
|||||||
update(
|
update(
|
||||||
currentState: MaxPluginState,
|
currentState: MaxPluginState,
|
||||||
newValue?: PropertyTypes,
|
newValue?: PropertyTypes,
|
||||||
_delta?: CollapsedDelta,
|
|
||||||
): MaxPluginState {
|
): MaxPluginState {
|
||||||
const numValue = newValue as number;
|
const numValue = newValue as number;
|
||||||
if (currentState.max === undefined || numValue > currentState.max) {
|
if (currentState.max === undefined || numValue > currentState.max) {
|
||||||
@ -40,7 +38,6 @@ export class MaxPlugin<Target extends PropertyID> extends ResolverPlugin<MaxPlug
|
|||||||
|
|
||||||
resolve(
|
resolve(
|
||||||
state: MaxPluginState,
|
state: MaxPluginState,
|
||||||
_dependencies?: DependencyStates
|
|
||||||
): PropertyTypes | undefined {
|
): PropertyTypes | undefined {
|
||||||
return state.max;
|
return state.max;
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ export class MinPlugin<Target extends PropertyID> extends ResolverPlugin<MinPlug
|
|||||||
|
|
||||||
update(
|
update(
|
||||||
currentState: MinPluginState,
|
currentState: MinPluginState,
|
||||||
newValue: PropertyTypes,
|
newValue?: PropertyTypes,
|
||||||
): MinPluginState {
|
): MinPluginState {
|
||||||
const numValue = newValue as number;
|
const numValue = newValue as number;
|
||||||
if (currentState.min === undefined || numValue < currentState.min) {
|
if (currentState.min === undefined || numValue < currentState.min) {
|
||||||
|
@ -64,7 +64,7 @@ export class CustomResolver extends Lossy<Accumulator, Result> {
|
|||||||
* @param propertyId The key by which a plugin is registered
|
* @param propertyId The key by which a plugin is registered
|
||||||
* @returns The base name of the plugin
|
* @returns The base name of the plugin
|
||||||
*/
|
*/
|
||||||
pluginBasenameFromKey(propertyId: PropertyID): PropertyID {
|
pluginBasenameFromKey(propertyId: string): string {
|
||||||
return this.config[propertyId]?.name || propertyId;
|
return this.config[propertyId]?.name || propertyId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,12 +73,22 @@ export class CustomResolver extends Lossy<Accumulator, Result> {
|
|||||||
* @param alias The alias of the plugin
|
* @param alias The alias of the plugin
|
||||||
* @returns The key by which it is registered
|
* @returns The key by which it is registered
|
||||||
*/
|
*/
|
||||||
pluginKeyFromBasename(alias: PropertyID): PropertyID {
|
pluginKeyFromBasename(name: string): string {
|
||||||
const entry = Object.entries(this.config).find(([_, plugin]) => plugin.name === alias);
|
const entry = Object.entries(this.config).find(([_, plugin]) => plugin.name === name);
|
||||||
if (!entry) return alias;
|
if (!entry) return name;
|
||||||
return entry[0];
|
return entry[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private logGraph(): void {
|
||||||
|
// Log the final dependency graph
|
||||||
|
const graphLog: Record<string, string[]> = {};
|
||||||
|
this.dependencyGraph.forEach((deps, plugin) => {
|
||||||
|
graphLog[plugin] = Array.from(deps);
|
||||||
|
});
|
||||||
|
debug(`Dependency graph: ${JSON.stringify(graphLog, null, 2)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build the dependency graph for all plugins.
|
* Build the dependency graph for all plugins.
|
||||||
* We'll use the basenames of the plugins in the graph.
|
* We'll use the basenames of the plugins in the graph.
|
||||||
@ -87,16 +97,15 @@ export class CustomResolver extends Lossy<Accumulator, Result> {
|
|||||||
debug('Building dependency graph...');
|
debug('Building dependency graph...');
|
||||||
|
|
||||||
// Initialize the graph with all plugins
|
// Initialize the graph with all plugins
|
||||||
Object.keys(this.config).forEach(propertyId => {
|
Object.keys(this.config).forEach(pluginKey => {
|
||||||
const pluginId = this.pluginBasenameFromKey(propertyId);
|
this.dependencyGraph.set(pluginKey, new Set());
|
||||||
this.dependencyGraph.set(pluginId, new Set());
|
debug(`Added plugin node: ${pluginKey}`);
|
||||||
debug(`Added plugin node: ${pluginId} (from property: ${propertyId})`);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
debug('Processing plugin dependencies...');
|
debug('Processing plugin dependencies...');
|
||||||
// Add edges based on dependencies
|
// Add edges based on dependencies
|
||||||
Object.entries(this.config).forEach(([propertyId, plugin]) => {
|
Object.entries(this.config).forEach(([pluginKey, plugin]) => {
|
||||||
const pluginId = plugin.name || propertyId;
|
const pluginId = plugin.name || pluginKey;
|
||||||
const deps = plugin.dependencies || [];
|
const deps = plugin.dependencies || [];
|
||||||
|
|
||||||
if (deps.length === 0) {
|
if (deps.length === 0) {
|
||||||
@ -108,29 +117,27 @@ export class CustomResolver extends Lossy<Accumulator, Result> {
|
|||||||
deps.forEach((depId: string) => {
|
deps.forEach((depId: string) => {
|
||||||
// This dependency may have an alias in our current config
|
// This dependency may have an alias in our current config
|
||||||
const depKey = this.pluginKeyFromBasename(depId);
|
const depKey = this.pluginKeyFromBasename(depId);
|
||||||
debug(`Processing dependency: ${depId} (resolved to key: ${depKey}) for plugin ${pluginId}`);
|
debug(`Processing dependency ${depKey} for plugin ${pluginKey}`);
|
||||||
|
|
||||||
if (!this.config[depKey]) {
|
if (!this.config[depKey]) {
|
||||||
const errorMsg = `Dependency ${depId} not found for plugin ${propertyId}`;
|
// TODO: This could still be a property, not a plugin
|
||||||
|
const errorMsg = `Dependency ${depKey} not found for plugin ${pluginKey}`;
|
||||||
debug(`Error: ${errorMsg}`);
|
debug(`Error: ${errorMsg}`);
|
||||||
throw new Error(errorMsg);
|
throw new Error(errorMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the dependency edge
|
// Add the dependency edge
|
||||||
this.dependencyGraph.get(depId)?.add(pluginId);
|
const dep = this.dependencyGraph.get(depKey)
|
||||||
debug(`Added edge: ${depId} -> ${pluginId}`);
|
if (!dep) {
|
||||||
|
throw new Error(`Dependency ${depKey} not found in dependency graph`);
|
||||||
|
}
|
||||||
|
dep.add(pluginKey);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Log the final dependency graph
|
|
||||||
const graphLog: Record<string, string[]> = {};
|
|
||||||
this.dependencyGraph.forEach((deps, plugin) => {
|
|
||||||
graphLog[plugin] = Array.from(deps);
|
|
||||||
});
|
|
||||||
|
|
||||||
debug('Dependency graph construction complete');
|
debug('Dependency graph construction complete');
|
||||||
debug(`Config: ${JSON.stringify(this.config, null, 2)}`);
|
debug(`Config: ${JSON.stringify(this.config, null, 2)}`);
|
||||||
debug(`Dependency graph: ${JSON.stringify(graphLog, null, 2)}`);
|
this.logGraph();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -188,36 +195,28 @@ export class CustomResolver extends Lossy<Accumulator, Result> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the resolved states of all dependencies for a plugin
|
* Get the resolved states of all dependencies for a plugin
|
||||||
* @param entityState The state of the entity
|
* @param entityPluginStates The state of the entity
|
||||||
* @param dependencies The dependencies to resolve
|
* @param dependencies The dependencies to resolve
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
private getDependencyStates(
|
private getDependencyStates(
|
||||||
entityState: EntityState,
|
entityPluginStates: EntityState,
|
||||||
plugin: ResolverPlugin<unknown, string>
|
pluginKey: string
|
||||||
): DependencyStates {
|
): DependencyStates {
|
||||||
const dependencyStates = {} as DependencyStates;
|
const plugin = this.config[pluginKey];
|
||||||
|
if (!plugin) throw new Error(`Plugin ${pluginKey} not found`);
|
||||||
|
|
||||||
for (const depId of plugin.dependencies || []) {
|
const dependencyStates: DependencyStates = {};
|
||||||
const depKey = this.pluginKeyFromBasename(depId);
|
|
||||||
|
for (const depKey of this.executionOrder) {
|
||||||
|
if (depKey === pluginKey) continue;
|
||||||
const depPlugin = this.config[depKey];
|
const depPlugin = this.config[depKey];
|
||||||
|
if (depPlugin) {
|
||||||
// TODO: If this is not a plugin, see if it's an entity property, and include it
|
if (!entityPluginStates[depKey]) {
|
||||||
|
dependencyStates[depKey] = depPlugin.initialize(dependencyStates);
|
||||||
const depValue = entityState[depKey];
|
entityPluginStates[depKey] = dependencyStates[depKey];
|
||||||
debug(`depId: ${depId}, depKey: ${depKey}, depPlugin: ${JSON.stringify(depPlugin)}, depValue: ${JSON.stringify(depValue)}`)
|
}
|
||||||
if (depValue) {
|
dependencyStates[depKey] = depPlugin.resolve(entityPluginStates[depKey], dependencyStates);
|
||||||
// Resolve the dependency's dependencies first
|
|
||||||
const depDependencies = this.getDependencyStates(
|
|
||||||
entityState,
|
|
||||||
depPlugin
|
|
||||||
);
|
|
||||||
|
|
||||||
// Resolve the dependency's state
|
|
||||||
dependencyStates[depId] = depPlugin.resolve(
|
|
||||||
depValue,
|
|
||||||
depDependencies
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,14 +229,13 @@ export class CustomResolver extends Lossy<Accumulator, Result> {
|
|||||||
}
|
}
|
||||||
const entityState = acc[entityId];
|
const entityState = acc[entityId];
|
||||||
|
|
||||||
for (const pluginId of this.executionOrder) {
|
for (const pluginKey of this.executionOrder) {
|
||||||
const pluginKey = this.pluginKeyFromBasename(pluginId);
|
|
||||||
const plugin = this.config[pluginKey];
|
const plugin = this.config[pluginKey];
|
||||||
if (!plugin) throw new Error(`Plugin for property ${pluginId} not found`);
|
if (!plugin) throw new Error(`Plugin ${pluginKey} not found`);
|
||||||
|
|
||||||
// We need to resolve dependencies, including entity properties that are not plugins.
|
// We need to resolve dependencies, including entity properties that are not plugins.
|
||||||
const dependencies = this.getDependencyStates(entityState, plugin);
|
const dependencies = this.getDependencyStates(entityState, pluginKey);
|
||||||
debug('Dependencies for', pluginId, ':', JSON.stringify(dependencies));
|
debug('Dependencies for', pluginKey, ':', JSON.stringify(dependencies));
|
||||||
|
|
||||||
// Initialize the plugin if it hasn't been initialized yet
|
// Initialize the plugin if it hasn't been initialized yet
|
||||||
entityState[pluginKey] = entityState[pluginKey] ?? plugin.initialize(dependencies);
|
entityState[pluginKey] = entityState[pluginKey] ?? plugin.initialize(dependencies);
|
||||||
@ -276,6 +274,8 @@ export class CustomResolver extends Lossy<Accumulator, Result> {
|
|||||||
// It's possible that there are multiple deltas in this set with the same property ID.
|
// It's possible that there are multiple deltas in this set with the same property ID.
|
||||||
// That can only happen if they are part of a transaction. Otherwise this function is
|
// That can only happen if they are part of a transaction. Otherwise this function is
|
||||||
// only called once per delta, per entity affected.
|
// only called once per delta, per entity affected.
|
||||||
|
// TODO: More flexible/robust error handling protocols?
|
||||||
|
// Some views might be more tolerant of errors than others.
|
||||||
throw new Error(`Duplicate property ID ${propertyId} found in delta ${delta.id}`);
|
throw new Error(`Duplicate property ID ${propertyId} found in delta ${delta.id}`);
|
||||||
}
|
}
|
||||||
deltaPropertyValues[propertyId] = pointer[propertyId];
|
deltaPropertyValues[propertyId] = pointer[propertyId];
|
||||||
@ -290,9 +290,10 @@ export class CustomResolver extends Lossy<Accumulator, Result> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update the plugin state with the new delta
|
// Update the plugin state with the new delta
|
||||||
const dependencies = this.getDependencyStates(entityState, plugin);
|
const dependencies = this.getDependencyStates(entityState, pluginKey);
|
||||||
entityState[pluginKey] = plugin.applyUpdate(pluginState, propertyValue, updateDelta, dependencies);
|
entityState[pluginKey] = plugin.applyUpdate(pluginState, propertyValue, updateDelta, dependencies);
|
||||||
debugState(`Updated entity state for ${entityId}:`, JSON.stringify(entityState[pluginKey]));
|
debugState(`Updated state for entity ${entityId} plugin ${pluginKey}:`,
|
||||||
|
JSON.stringify(entityState[pluginKey]));
|
||||||
}
|
}
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
@ -312,21 +313,20 @@ export class CustomResolver extends Lossy<Accumulator, Result> {
|
|||||||
properties: {}
|
properties: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const propertyId of this.executionOrder) {
|
for (const pluginKey of this.executionOrder) {
|
||||||
const pluginKey = this.pluginKeyFromBasename(propertyId);
|
|
||||||
const plugin = this.config[pluginKey];
|
const plugin = this.config[pluginKey];
|
||||||
if (!plugin) throw new Error(`Plugin for property ${propertyId} not found`);
|
if (!plugin) throw new Error(`Plugin ${pluginKey} not found`);
|
||||||
|
|
||||||
debug(`Processing property: ${propertyId} (key: ${pluginKey})`);
|
debug(`Processing property: ${pluginKey}`);
|
||||||
const dependencies = this.getDependencyStates(acc[entityId], plugin);
|
const dependencies = this.getDependencyStates(acc[entityId], pluginKey);
|
||||||
debug(`Dependencies for ${propertyId}:`, JSON.stringify(dependencies));
|
debug(`Dependencies for ${pluginKey}:`, JSON.stringify(dependencies));
|
||||||
const state = acc[entityId][pluginKey] || plugin.initialize(dependencies);
|
const state = acc[entityId][pluginKey] || plugin.initialize(dependencies);
|
||||||
debug(`State for ${propertyId}:`, JSON.stringify(state));
|
debug(`State for ${pluginKey}:`, JSON.stringify(state));
|
||||||
|
|
||||||
const resolvedValue = plugin.resolve(state, dependencies);
|
const resolvedValue = plugin.resolve(state, dependencies);
|
||||||
if (resolvedValue === undefined) throw new Error(`Resolved value for property ${propertyId} is undefined`)
|
if (resolvedValue === undefined) throw new Error(`Resolved value for property ${pluginKey} is undefined`)
|
||||||
|
|
||||||
debug(`Resolved value for ${propertyId}:`, resolvedValue);
|
debug(`Resolved value for ${pluginKey}:`, resolvedValue);
|
||||||
result[entityId].properties[pluginKey] = resolvedValue;
|
result[entityId].properties[pluginKey] = resolvedValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user