Skip to content

Commit

Permalink
refine snapshot rendering
Browse files Browse the repository at this point in the history
  • Loading branch information
stackdump committed Jan 13, 2024
1 parent 10e0f03 commit e94af59
Show file tree
Hide file tree
Showing 7 changed files with 122 additions and 25 deletions.
57 changes: 45 additions & 12 deletions model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,10 +120,10 @@ export interface Model {
addTransition: (coords: { x: number; y: number }) => boolean;
capacityVector: () => Vector;
def: PetriNet;
swapArc: (id: number) => void;
deleteArc: (id: number) => void;
deletePlace: (id: string) => void;
deleteTransition: (id: string) => void;
dsl: { fn: Fn; cell: Cell; role: Role };
emptyVector: () => Vector;
fire: (state: Vector, action: string, multiple: number, resolve?: (res: Result) => void, reject?: (res: Result) => void) => Result;
getObject: (id: string) => Place | Transition;
Expand All @@ -137,7 +137,6 @@ export interface Model {
renamePlace: (oldLabel: string, newLabel: string) => void;
renameTransition: (oldLabel: string, newLabel: string) => void;
setArcWeight: (offset: number, weight: number) => boolean;
testFire: (state: Vector, action: string, multiple: number) => Result;
toObject: (mode?: "sparse" | "full") => any;
toggleInhibitor: (id: number) => boolean;
transitionSeq: () => string;
Expand All @@ -147,7 +146,7 @@ export type ModelDeclaration = {
modelType: ModelType;
version: Version;
places: {
[key: string]: { initial?: number; capacity?: number; x: number; y: number };
[key: string]: { offset: number; initial?: number; capacity?: number; x: number; y: number };
};
transitions: {
[key: string]: { role?: string; x: number; y: number };
Expand Down Expand Up @@ -628,6 +627,30 @@ export function newModel({schema, declaration, type}: ModelOptions): Model {
return a.source?.place?.label !== id && a.target?.place?.label !== id;
});
}

function swapArc(id: number): void {
const arc = def.arcs[id];
const place = arc.source?.place || arc.target?.place;
const transition = arc.source?.transition || arc.target?.transition;

if (arc.inhibit) {
const g = transition.guards.get(place.label);
if (g) {
g.inverted = !g.inverted;
}
} else {
transition.delta[place.offset] = 0-transition.delta[place.offset];
}

if (arc.source?.place) {
arc.source = { transition };
arc.target = { place };
} else {
arc.source = { place };
arc.target = { transition };
}
}

function deleteArc(id: number): void {
const arc = def.arcs[id];
const source = arc.source?.place || arc.source?.transition;
Expand Down Expand Up @@ -907,13 +930,24 @@ export function newModel({schema, declaration, type}: ModelOptions): Model {
throw new Error("invalid model version: " + obj.version);
}
const nodes = new Map<string, PlaceNode | TxNode>();
const placeList = [];
for (const label in obj.places) {
const {initial, capacity, x, y} = obj.places[label];
const {
initial,
offset,
capacity,
x,
y
} = obj.places[label];
placeList.push({label, offset, initial: initial || 0, capacity: capacity || 0, x, y});
}
placeList.sort((a, b) => a.offset - b.offset);
for (const {label, initial, capacity, x, y} of placeList) {
nodes.set(label, cell(label, initial, capacity, {x, y}));
}
for (const label in obj.transitions) {
const {x, y} = obj.transitions[label];
nodes.set(label, fn(label, {label: "default"}, {x, y}));
const {x, y, role} = obj.transitions[label];
nodes.set(label, fn(label, {label: role || "default"}, {x, y}));
}
for (const arc of obj.arcs) {
const {source, target, weight, inhibit, reentry} = arc;
Expand All @@ -930,9 +964,9 @@ export function newModel({schema, declaration, type}: ModelOptions): Model {
throw new Error("invalid arc target: "+target);
}
if (inhibit) {
sourceObj.guard(weight, targetObj);
sourceObj.guard(weight || 1, targetObj);
} else {
sourceObj.tx(weight, targetObj);
sourceObj.tx(weight || 1, targetObj);
}
if (reentry) {
throw new Error("reentry must use transition->place arc");
Expand All @@ -942,9 +976,9 @@ export function newModel({schema, declaration, type}: ModelOptions): Model {
throw new Error("invalid arc");
}
if (inhibit) {
sourceObj.guard(weight, targetObj);
sourceObj.guard(weight || 1, targetObj);
} else {
sourceObj.tx(weight, targetObj);
sourceObj.tx(weight || 1, targetObj);
}
if (reentry) {
sourceObj.reentry(targetObj);
Expand All @@ -970,10 +1004,10 @@ export function newModel({schema, declaration, type}: ModelOptions): Model {
addTransition,
capacityVector,
def,
swapArc,
deleteArc,
deletePlace,
deleteTransition,
dsl: {fn, cell, role},
emptyVector,
fire,
getObject,
Expand All @@ -987,7 +1021,6 @@ export function newModel({schema, declaration, type}: ModelOptions): Model {
renamePlace,
renameTransition,
setArcWeight,
testFire,
toObject,
toggleInhibitor,
transitionSeq,
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@pflow-dev/metamodel",
"version": "0.8.0",
"version": "0.10.0",
"main": "/index.js",
"types": "/index.d.ts",
"description": "create workflows and petriNets with a DSL",
Expand Down
26 changes: 19 additions & 7 deletions snapshot.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Model, Place, Transition, Vector} from "./model";
import {Model, ModelType, Place, Transition, Vector} from "./model";

const tokenTemplate = ({p, tokens}: { p: Place; tokens: number }) => {
if (tokens === 0) {
Expand Down Expand Up @@ -33,6 +33,9 @@ const arcTemplate = ({stroke, markerEnd, weight, x1, y1, x2, y2, midX, offsetX,
`<line stroke="${stroke}" marker-end="${markerEnd}" x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}" />` +
`<text x="${midX - offsetX}" y="${midY + offsetY}" >${weight}</text>`;

const arcTemplateNoLabel = ({stroke, markerEnd, x1, y1, x2, y2}: ArcParams) =>
`<line stroke="${stroke}" marker-end="${markerEnd}" x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}" />`;

const transitionTemplate = ({fill, stroke, t}: { fill: string; stroke: string; t: Transition }) =>
`<rect width="30" height="30" fill="${fill}" stroke="${stroke}" rx="${4}" x="${t.position.x - 15}" y="${t.position.y - 15}" />` +
`<text font-size="smaller" x="${t.position.x - 15}" y="${t.position.y - 20}" >${t.label}</text>`;
Expand Down Expand Up @@ -94,14 +97,14 @@ function getArcPoints(
type HashChar = "#" | "%32";

export function snapshot(model: Model, options?: { state?: Vector; hashChar?: HashChar }) {
const state = options?.state;
const state = options?.state ?? model.initialVector();
const hashChar = options?.hashChar ? options.hashChar : "#"; // use "%32" for data URI
const {transitions, places} = model.def;
const page = model.getSize(); // FIXME: port from other repo
let transitionTags = "";
transitions.forEach((t) => {
function getFill() {
const res = model.testFire(state, t.label, 1);
const res = model.fire([...state], t.label, 1);
if (res.ok) {
return "#62fa75";
}
Expand All @@ -123,20 +126,29 @@ export function snapshot(model: Model, options?: { state?: Vector; hashChar?: Ha
placeIndex[p.offset] = p;
});
let arcTags = "";

function makeArc(params: ArcParams) {
if (model.def.type !== ModelType.petriNet) {
return arcTemplateNoLabel(params);
} else {
return arcTemplate(params);
}
}

transitions.forEach((t) => {
t.guards.forEach((v, k) => {
const place = places.get(k);
if (v.inverted) {
const pts = getArcPoints({source: t, target: place});
arcTags += arcTemplate({
arcTags += makeArc({
...pts,
stroke: "black",
markerEnd: `url(${hashChar}markerInhibit1)`,
weight: Math.abs(v.delta[place.offset])
});
} else {
const pts = getArcPoints({source: place, target: t});
arcTags += arcTemplate({
arcTags += makeArc({
...pts,
stroke: "black",
markerEnd: `url(${hashChar}markerInhibit1)`,
Expand All @@ -150,15 +162,15 @@ export function snapshot(model: Model, options?: { state?: Vector; hashChar?: Ha
const v = t.delta[i];
if (v > 0) {
const pts = getArcPoints({source: t, target: placeIndex[i]});
arcTags += arcTemplate({
arcTags += makeArc({
...pts,
stroke: "black",
markerEnd: `url(${hashChar}markerArrow1)`,
weight: v,
});
} else if (v < 0) {
const pts = getArcPoints({target: t, source: placeIndex[i]});
arcTags += arcTemplate({
arcTags += makeArc({
...pts,
stroke: "black",
markerEnd: `url(${hashChar}markerArrow1)`,
Expand Down
3 changes: 1 addition & 2 deletions test/model.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,7 @@ describe("metamodel", () => {

it("should still work for invalid elementary models", () => {
const mm = testModel({declaration: testElementaryInvalid, type: ModelType.petriNet});
const svg = snapshot(mm.m, {});
fs.writeFileSync("test.svg", svg);


mm.trigger("t1", {expectPass: true});
mm.trigger("t1", {expectFail: true});
Expand Down
2 changes: 1 addition & 1 deletion test/objectLoader.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const inhibitTest: ModelDeclaration = {
modelType: ModelType.petriNet,
version: "v0",
places: {
foo: {x: 0, y: 0},
foo: {offset: 0, x: 0, y: 0},
},
transitions: {
bar: {x: 0, y: 0},
Expand Down
53 changes: 53 additions & 0 deletions test/wf.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import {ModelDeclaration, ModelType, newModel, snapshot} from "../";
import fs from "fs";

const wfNet: ModelDeclaration = {
"modelType": ModelType.workflow,
"version": "v0",
"places": {
"entry": { "offset": 0, "initial": 1, "capacity": 0, "x": 852, "y": 54 },
"exit": { "offset": 1, "initial": 0, "capacity": 0, "x": 863, "y": 546 },
"place2": { "offset": 2, "initial": 0, "capacity": 0, "x": 738, "y": 367 },
"place3": { "offset": 3, "initial": 0, "capacity": 0, "x": 962, "y": 359 }
},
"transitions": {
"txn0": { "x": 728, "y": 155 },
"txn1": { "x": 964, "y": 147 },
"txn6": { "x": 861, "y": 455 }
},
"arcs": [
{ "source": "entry", "target": "txn0", "weight": 1 },
{ "source": "entry", "target": "txn1", "weight": 1 },
{ "source": "place3", "target": "txn6", "weight": 1 },
{ "source": "place2", "target": "txn6", "weight": 1 },
{ "source": "txn6", "target": "exit", "weight": 1 },
{ "source": "txn0", "target": "place2", "weight": 1 },
{ "source": "txn1", "target": "place3", "weight": 1 }
]
};

describe("metamodel", () => {
describe("workflow", () => {
it("should not work for invalid models", () => {
const m = newModel({schema: "wfNet", declaration: wfNet, type: wfNet["modelType"]});
expect(m.def.type).toEqual("workflow");
const svg = snapshot(m, { state: [0, 0, 1, 0]});
fs.writeFileSync("test.svg", svg); // FIXME: assert this generates a valid SVG
const state = m.initialVector();
const entry = m.def.places.get("entry");
expect(entry).toBeDefined();
expect(entry.offset).toEqual(0);
expect(state[entry.offset]).toEqual(1);
expect(entry.initial).toEqual(1);
expect(entry.capacity).toEqual(0);

expect(m.fire(state, "txn6", 1).ok).toBeFalsy();
expect(state).toEqual([1, 0, 0, 0]);
expect(m.fire(state, "txn0", 1).ok).toBeTruthy();
expect(state).toEqual([0, 0, 1, 0]);
expect(m.fire(state, "txn6", 1).ok).toBeTruthy();
expect(state).toEqual([0, 1, 0, 0]);
});
});
});

0 comments on commit e94af59

Please sign in to comment.