diff --git a/package.json b/package.json index 6f4087f..d8c58fc 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,8 @@ "author": "Yuu6883", "license": "MIT", "devDependencies": { + "@swc-node/register": "^1.5.1", + "@swc/core": "^1.7.26", "@types/ace": "^0.0.48", "@types/file-saver": "^2.0.5", "@types/react": "^18.0.14", @@ -44,8 +46,6 @@ "webpack-dev-server": "^4.9.2" }, "dependencies": { - "@swc-node/register": "^1.5.1", - "canvas": "^2.11.2", - "seedrandom": "^3.0.5" + "canvas": "^2.11.2" } } diff --git a/src/helpers/helper.ts b/src/helpers/helper.ts index df13941..b069dfc 100644 --- a/src/helpers/helper.ts +++ b/src/helpers/helper.ts @@ -1,4 +1,4 @@ -import { PRNG } from "seedrandom"; +import { Random } from "../random"; interface WritableArray { readonly length: number; @@ -111,7 +111,7 @@ export class Helper { public static shuffleFill>( array: T, - rng: PRNG + rng: Random ) { for (let i = 0; i < array.length; i++) { const j = range(rng, i + 1); @@ -120,7 +120,7 @@ export class Helper { } } - public static pick>(array: T, rng: PRNG) { + public static pick>(array: T, rng: Random) { return array[range(rng, array.length)]; } @@ -191,7 +191,7 @@ export class Helper { } // exclusive -export const range = (rng: PRNG, upper: number) => Math.floor(rng() * upper); +export const range = (rng: Random, upper: number) => Math.floor(rng.nextDouble() * upper); export type vec3 = [number, number, number]; export type vec4 = [number, number, number, number]; diff --git a/src/interpreter.ts b/src/interpreter.ts index 49eca46..b38f2f5 100644 --- a/src/interpreter.ts +++ b/src/interpreter.ts @@ -1,4 +1,4 @@ -import seedrandom, { PRNG } from "seedrandom"; +import { Random } from "./random"; import { Grid } from "./grid"; import { vec3 } from "./helpers/helper"; import { SymmetryHelper } from "./helpers/symmetry"; @@ -14,7 +14,7 @@ export class Interpreter { public startgrid: Grid; origin: boolean; - public rng: PRNG; + public rng: Random; public time = 0; public readonly changes: vec3[] = []; @@ -62,7 +62,7 @@ export class Interpreter { seed: number, steps: number ): Generator<[Uint8Array, string, number, number, number]> { - this.rng = seedrandom(seed.toString()); + this.rng = new Random(seed); this.grid = this.startgrid; this.grid.clear(); diff --git a/src/mj-nodes/all.ts b/src/mj-nodes/all.ts index 4f5cc94..f884d22 100644 --- a/src/mj-nodes/all.ts +++ b/src/mj-nodes/all.ts @@ -107,7 +107,7 @@ export class AllNode extends RuleNode { firstHeuristic = heuristic; firstHeuristicComputed = true; } - const u = this.ip.rng.double(); + const u = this.ip.rng.nextDouble(); list.push([ m, this.temperature > 0 diff --git a/src/mj-nodes/convchain.ts b/src/mj-nodes/convchain.ts index 313c32a..3210360 100644 --- a/src/mj-nodes/convchain.ts +++ b/src/mj-nodes/convchain.ts @@ -144,7 +144,7 @@ export class ConvChainNode extends Node { continue; } if (temperature != 1) q = Math.pow(q, 1.0 / temperature); - if (q > ip.rng.double()) this.toggle(state, r); + if (q > ip.rng.nextDouble()) this.toggle(state, r); } this.counter++; diff --git a/src/mj-nodes/convolution.ts b/src/mj-nodes/convolution.ts index aa28fe7..32b6a90 100644 --- a/src/mj-nodes/convolution.ts +++ b/src/mj-nodes/convolution.ts @@ -143,7 +143,7 @@ export class ConvolutionNode extends Node { if ( input == rule.input && rule.output != grid.state[i] && - (rule.p == 1.0 || this.ip.rng.double() < rule.p) + (rule.p == 1.0 || this.ip.rng.nextDouble() < rule.p) ) { let success = true; if (rule.sums != null) { diff --git a/src/mj-nodes/one.ts b/src/mj-nodes/one.ts index 5270311..b3d3d1b 100644 --- a/src/mj-nodes/one.ts +++ b/src/mj-nodes/one.ts @@ -1,4 +1,4 @@ -import { PRNG } from "seedrandom"; +import { Random } from "../random"; import { Field } from "../field"; import { Grid } from "../grid"; import { BoolArray2D } from "../helpers/datastructures"; @@ -61,7 +61,7 @@ export class OneNode extends RuleNode { } } - randomMatch(rng: PRNG): vec4 { + randomMatch(rng: Random): vec4 { const { grid, matchMask, matches } = this; if (this.potentials) { @@ -112,7 +112,7 @@ export class OneNode extends RuleNode { firstHeuristic = heuristic; firstHeuristicComputed = true; } - const u = rng.double(); + const u = rng.nextDouble(); const key = this.temperature > 0 ? Math.pow( diff --git a/src/mj-nodes/overlap.ts b/src/mj-nodes/overlap.ts index 7e3ce82..4a95d96 100644 --- a/src/mj-nodes/overlap.ts +++ b/src/mj-nodes/overlap.ts @@ -1,4 +1,3 @@ -import { alea } from "seedrandom"; import { Grid } from "../grid"; import { Loader } from "../loader"; import { Array2D } from "../helpers/datastructures"; @@ -6,10 +5,12 @@ import { Helper } from "../helpers/helper"; import { SymmetryHelper } from "../helpers/symmetry"; import { WFCNode } from "."; +import { Random } from "../random"; // A bit slower than C# (130ms vs 90ms, WaveFlower, ryzen 5800x) export class OverlapNode extends WFCNode { - protected static state_rng = alea("", { entropy: true }); + // protected static state_rng = alea("", { entropy: true }); + protected static state_rng = new Random(); private patterns: Array2D; private votes: Array2D; @@ -245,7 +246,7 @@ export class OverlapNode extends WFCNode { const offset = i * cols; for (let c = 0; c < cols; c++) { - const value = buf[offset + c] + 0.1 * rng.double(); + const value = buf[offset + c] + 0.1 * rng.nextDouble(); if (value > max) { argmax = c; max = value; diff --git a/src/mj-nodes/parallel.ts b/src/mj-nodes/parallel.ts index 8af5e93..2a42842 100644 --- a/src/mj-nodes/parallel.ts +++ b/src/mj-nodes/parallel.ts @@ -19,7 +19,7 @@ export class ParallelNode extends RuleNode { const grid = this.grid; const rule = this.rules[r]; - if (ip.rng.double() > rule.p) return; + if (ip.rng.nextDouble() > rule.p) return; this.last |= 1 << r; rule.jit_apply_kernel( grid.state, diff --git a/src/mj-nodes/path.ts b/src/mj-nodes/path.ts index b8c16a1..1d7e4ce 100644 --- a/src/mj-nodes/path.ts +++ b/src/mj-nodes/path.ts @@ -1,4 +1,4 @@ -import seedrandom, { PRNG } from "seedrandom"; +import { Random } from "../random"; import { Grid } from "../grid"; import { Helper, vec3, vec4 } from "../helpers/helper"; @@ -92,7 +92,7 @@ export class PathNode extends Node { ) return RunState.FAIL; - const local = seedrandom(this.ip.rng.int32().toString()); + const local = new Random(this.ip.rng.next()); let min = MX * MY * MZ, max = -1; let argmin: vec3 = [-1, -1, -1]; @@ -102,7 +102,7 @@ export class PathNode extends Node { const g = generations[px + py * MX + pz * MX * MY]; if (g == -1) continue; const dg = g; - const noise = 0.1 * local.double(); + const noise = 0.1 * local.nextDouble(); if (dg + noise < min) { min = dg + noise; @@ -159,7 +159,7 @@ export class PathNode extends Node { dy: number, dz: number, generations: Int32Array, - rng: PRNG + rng: Random ): vec3 { const candidates: vec3[] = []; const { grid, vertices, edges, inertia } = this; @@ -217,7 +217,7 @@ export class PathNode extends Node { if (inertia && (dx != 0 || dy != 0 || dz != 0)) { let maxScalar = -4; for (const [cx, cy, cz] of candidates) { - const noise = 0.1 * rng.double(); + const noise = 0.1 * rng.nextDouble(); const cos = (cx * dx + cy * dy + cz * dz) / Math.sqrt( diff --git a/src/mj-nodes/rule.ts b/src/mj-nodes/rule.ts index 3228737..e20d942 100644 --- a/src/mj-nodes/rule.ts +++ b/src/mj-nodes/rule.ts @@ -282,7 +282,7 @@ export abstract class RuleNode extends Node { this instanceof AllNode, this.limit, this.depthCoefficient, - this.ip.rng.int32(), + this.ip.rng.next(), true // viz ).run() : Search.run( @@ -296,7 +296,7 @@ export abstract class RuleNode extends Node { this instanceof AllNode, this.limit, this.depthCoefficient, - this.ip.rng.int32(), + this.ip.rng.next(), true // viz ); } diff --git a/src/mj-nodes/tile.ts b/src/mj-nodes/tile.ts index 34eaf5b..dab15c9 100644 --- a/src/mj-nodes/tile.ts +++ b/src/mj-nodes/tile.ts @@ -1,4 +1,3 @@ -import { alea } from "seedrandom"; import { Grid } from "../grid"; import { Loader } from "../loader"; import { Array2D, Array3Dflat, BoolArray3D } from "../helpers/datastructures"; @@ -6,11 +5,13 @@ import { Helper } from "../helpers/helper"; import { SymmetryHelper } from "../helpers/symmetry"; import { WFCNode } from "."; +import { Random } from "../random"; const FALSE = (_) => false; export class TileNode extends WFCNode { - protected static state_rng = alea("", { entropy: true }); + // protected static state_rng = alea("", { entropy: true }); + protected static state_rng = new Random(); private tiledata: Uint8Array[]; @@ -480,7 +481,7 @@ export class TileNode extends WFCNode { let max = -1.0; let argmax = 0xff; for (let c = 0; c < v.length; c++) { - const vote = v[c] + 0.1 * rng.double(); + const vote = v[c] + 0.1 * rng.nextDouble(); if (vote > max) { argmax = c; max = vote; diff --git a/src/mj-nodes/wfc.ts b/src/mj-nodes/wfc.ts index 1da3d8c..f1e4d95 100644 --- a/src/mj-nodes/wfc.ts +++ b/src/mj-nodes/wfc.ts @@ -1,4 +1,4 @@ -import seedrandom, { PRNG } from "seedrandom"; +import { Random } from "../random"; import { Grid } from "../grid"; import { Array3D, BoolArray2D } from "../helpers/datastructures"; import { Helper } from "../helpers/helper"; @@ -34,7 +34,7 @@ export abstract class WFCNode extends Branch { public name: string; private firstgo = true; - protected rng: PRNG; + protected rng: Random; public override async load( elem: Element, @@ -121,7 +121,7 @@ export abstract class WFCNode extends Branch { const goodseed = this.goodSeed(); if (goodseed === null) return RunState.FAIL; - this.rng = seedrandom(goodseed.toString()); + this.rng = new Random(goodseed); this.stacksize = 0; this.wave.copyFrom(this.startwave, this.shannon); this.firstgo = false; @@ -147,8 +147,8 @@ export abstract class WFCNode extends Branch { goodSeed(): number { for (let k = 0; k < this.tries; k++) { let obs = 0; - const seed = this.ip.rng.int32(); - this.rng = seedrandom(seed.toString()); + const seed = this.ip.rng.next(); + this.rng = new Random(seed); this.stacksize = 0; this.wave.copyFrom(this.startwave, this.shannon); @@ -178,7 +178,7 @@ export abstract class WFCNode extends Branch { return null; } - nextUnobservedNode(rng: PRNG) { + nextUnobservedNode(rng: Random) { const { grid, wave, periodic, shannon } = this; const { MX, MY, MZ } = grid; @@ -197,7 +197,7 @@ export abstract class WFCNode extends Branch { ? wave.entropies[i] : remainingValues; if (remainingValues > 1 && entropy <= min) { - const noise = 1e-6 * rng.double(); + const noise = 1e-6 * rng.nextDouble(); if (entropy + noise < min) { min = entropy + noise; argmin = i; @@ -207,11 +207,11 @@ export abstract class WFCNode extends Branch { return argmin; } - observe(node: number, rng: PRNG) { + observe(node: number, rng: Random) { const w = this.wave.data.row(node); for (let t = 0; t < this.P; t++) this.distribution[t] = w.get(t) ? this.weights[t] : 0; - const r = Helper.sampleWeights(this.distribution, rng.double()); + const r = Helper.sampleWeights(this.distribution, rng.nextDouble()); for (let t = 0; t < this.P; t++) if (w.get(t) !== (t === r)) this.ban(node, t); } diff --git a/src/node/program.ts b/src/node/program.ts index 9277f21..a4796bd 100644 --- a/src/node/program.ts +++ b/src/node/program.ts @@ -1,4 +1,4 @@ -import seedrandom from "seedrandom"; +import { Random } from "./random"; import * as path from "path"; import * as fs from "fs"; @@ -45,7 +45,7 @@ export class Program { public static palette: Map = new Map(); - public static meta = seedrandom(); + public static meta = new Random(); public static loadPalette() { const ep = Loader.xmlParse(PaletteXML); @@ -248,7 +248,7 @@ export class Model { } public randomize() { - this._seed = Math.abs(Program.meta.int32()); + this._seed = Math.abs(Program.meta.next()); } private scaleTime(t: number) { diff --git a/src/random.ts b/src/random.ts new file mode 100644 index 0000000..5b91374 --- /dev/null +++ b/src/random.ts @@ -0,0 +1,124 @@ +/** + * Original Code: + * https://github.com/microsoft/referencesource/blob/master/mscorlib/system/random.cs + * https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Random.cs + * Converted to TypeScript By: x2nie + * Date: 2023-11-19 + * + */ + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +const Int32 = { + MinValue: Math.pow(2, 31) * -1, + MaxValue: Math.pow(2, 31) - 1 +} +const MBIG = Int32.MaxValue; +const MSEED = 161803398; + +export class Random { + // + // Member Variables + // + private inext: number; + private inextp: number; + //private int[] SeedArray = new int[56]; + private SeedArray: Int32Array; + + // public Random(Seed:number) {} + constructor(seed: number | undefined = undefined) { + this.inext = 0; + this.inextp = 21; + this.SeedArray = new Int32Array(56); + this.SeedArray.fill(0); + + if (seed === undefined) { + //this(Environment.TickCount); + seed = Math.floor(Math.random() * MBIG); + } + seed = Math.floor(seed); // = int()Seed + + let ii: number; + let mj: number, mk: number; + //Initialize our Seed array. + //This algorithm comes from Numerical Recipes in C (2nd Ed.) + let subtraction = (seed == Int32.MinValue) ? Int32.MaxValue : Math.abs(seed); + mj = MSEED - subtraction; + this.SeedArray[55] = mj; + mk = 1; + for (let i = 1; i < 55; i++) { //Apparently the range [1..55] is special (Knuth) and so we're wasting the 0'th position. + ii = (21 * i) % 55; + this.SeedArray[ii] = mk; + mk = mj - mk; + if (mk < 0) mk += MBIG; + mj = this.SeedArray[ii]; + } + for (let k = 1; k < 5; k++) { + for (let i = 1; i < 56; i++) { + this.SeedArray[i] -= this.SeedArray[1 + (i + 30) % 55]; + if (this.SeedArray[i] < 0) this.SeedArray[i] += MBIG; + } + } + } + + public next(maxValue: number = 0): number { + if (maxValue == 0) + return this.internalSample() + + if (maxValue < 0) { + throw new Error("ArgumentOutOfRange_MustBePositive"); + } + return Math.floor(this.sample() * maxValue); + } + + public nextDouble(): number { + return this.sample(); + } + + protected sample(): number { //Float.double + //Including this division at the end gives us significantly improved + //random number distribution. + + //? C# : + // return (this.InternalSample()*(1.0/MBIG)); + + //? however, it actually produces identical result with C# (tested on Chrome | Ubuntu 24.04 x64 | .NET v8.0.108 | 2024-09-28) + return this.internalSample() * (1.0 / MBIG); + + //? Old Implementation & Slow : + // const jsFloat = this.internalSample() * (1.0 / MBIG); + // const csharpDouble = jsFloat.toPrecision(15); //got string + // return Number(csharpDouble); // will identical to C# value + + //? Faster JavaScript & identical to C# : + // const f32arr = new Float32Array(1) + // f32arr[0] = this.internalSample()*(1.0/MBIG); + // return f32arr[0] + + //? same as above, but with reuse, like its fastest, but is not thread-safe + // CLAMPER[0] = this.internalSample()*OneBIGth; + // return CLAMPER[0] + } + + private internalSample(): number { // int + let retVal: number; + let locINext = this.inext; + let locINextp = this.inextp; + + if (++locINext >= 56) locINext = 1; + if (++locINextp >= 56) locINextp = 1; + + retVal = this.SeedArray[locINext] - this.SeedArray[locINextp]; + + if (retVal == MBIG) retVal--; + if (retVal < 0) retVal += MBIG; + + this.SeedArray[locINext] = retVal; + + this.inext = locINext; + this.inextp = locINextp; + + return retVal; + } +} \ No newline at end of file diff --git a/src/search.ts b/src/search.ts index bb0c517..5477fc1 100644 --- a/src/search.ts +++ b/src/search.ts @@ -1,4 +1,4 @@ -import seedrandom, { PRNG } from "seedrandom"; +import { Random } from "./random"; import { Array2D, HashMap, PriorityQueue } from "./helpers/datastructures"; import { Helper } from "./helpers/helper"; import { Observation } from "./observation"; @@ -124,7 +124,7 @@ export class Search { const frontier = new PriorityQueue<{ p: number; v: number }>( ({ p: p1 }, { p: p2 }) => p1 <= p2 ); - const rng = seedrandom(seed.toString()); + const rng = new Random(seed); frontier.enqueue({ v: 0, p: rootBoard.rank(rng, depthCoefficient) }); let record = rootBackwardEstimate + rootForwardEstimate; @@ -504,14 +504,14 @@ class Board { this.forwardEstimate = forwardEstimate; } - public rank(rng: PRNG, depthCoefficient: number) { + public rank(rng: Random, depthCoefficient: number) { const result = depthCoefficient < 0.0 ? 1000 - this.depth : this.forwardEstimate + this.backwardEstimate + 2.0 * depthCoefficient * this.depth; - return result + 0.0001 * rng.double(); + return result + 0.0001 * rng.nextDouble(); } // Path trace diff --git a/src/wasm/search.ts b/src/wasm/search.ts index eb85527..e6b0205 100644 --- a/src/wasm/search.ts +++ b/src/wasm/search.ts @@ -1,4 +1,4 @@ -import seedrandom from "seedrandom"; +import { Random } from "../random"; import { WasmInstance } from "."; import { HashMap, PriorityQueue } from "../helpers/datastructures"; import { Rule } from "../rule"; @@ -70,7 +70,7 @@ export class NativeSearch { seed, viz, } = this; - const rng = seedrandom(seed.toString()); + const rng = new Random(seed); lib.reset(); @@ -197,7 +197,7 @@ export class NativeSearch { const push_board = (ptr: number) => frontier.enqueue({ v: ptr, - p: board_rank(ptr, rng.double(), dcoeff), + p: board_rank(ptr, rng.nextDouble(), dcoeff), }); const root_board = new_board(elem); diff --git a/src/web/center.tsx b/src/web/center.tsx index 8b4b368..cc09e18 100644 --- a/src/web/center.tsx +++ b/src/web/center.tsx @@ -31,7 +31,13 @@ export const ControlPanel = observer(() => { ? `${model.MX}x${model.MY}x${model.MZ}` : `${model.MX}x${model.MY}`} {" "} - seed: {model.seed} + + seed: { + model.set_seed(ev.target.value) + }} />

{model.loading ? (

loading...

diff --git a/src/web/program.ts b/src/web/program.ts index a15c2a7..805eb22 100644 --- a/src/web/program.ts +++ b/src/web/program.ts @@ -1,4 +1,4 @@ -import seedrandom from "seedrandom"; +import { Random } from "../random"; import { action, computed, @@ -53,6 +53,42 @@ const Render3DTypes = { voxel: VoxelPathTracer, }; +class DebugLineHighlighter implements ace.Ace.MarkerLike { + range: ace.Ace.Range; + type: string; + renderer?: ace.Ace.MarkerRenderer; + clazz: string = 'debug-line'; + inFront: boolean; + id: number; + lineNo: number = -1; + public setLineNo(lineNo, editor:ace.Ace.Editor) { + if(lineNo != this.lineNo){ + this.lineNo = lineNo; + this.range = new ace.Range(this.lineNo,0, this.lineNo+1, 0); + //@ts-ignore + editor.session._emit("changeBackMarker"); + } + } + //constructor(editor: ace.Ace.edit) + public update (html, markerLayer, session:ace.Ace.EditSession, config) { + if(this.lineNo<0) return; + + // let y = getRandomInt(26) + let dynR = new ace.Range(this.lineNo,0, this.lineNo+1, 10); + // dynR=range3 + //range2 = dynR; + const w = dynR.clipRows(config.firstRow, config.lastRow); + if(w.isEmpty()) return; + debugger + + // var rangeToAddMarkerTo = dynR.toScreenRange(session); + // var rangeAsString = rangeToAddMarkerTo.toString(); + // console.log(config.firstRow, config.lastRow); + //console.log('halo',rangeAsString)// JSON.stringify({html, markerLayer, session, config}, null, 3)) + markerLayer.drawSingleLineMarker(html, dynR, 'debug-line', config) + } +} + export class Program { @observable.ref public static instance: Model = null; @@ -63,7 +99,7 @@ export class Program { @observable public static palette: Map = new Map(); - public static meta = seedrandom(); + public static meta = new Random(); public static readonly editor = ace.edit(null, { wrap: true, @@ -73,6 +109,9 @@ export class Program { mode: "ace/mode/xml", }); + public static readonly debugLineHighlighter = new DebugLineHighlighter(); + + @action public static loadPalette() { const ep = Loader.xmlParse(PaletteXML); @@ -225,6 +264,25 @@ export class Model { Program.editor.setValue(this.modelXML); Program.editor.clearSelection(); + Program.editor.session.clearBreakpoints(); + if(!Program.debugLineHighlighter.id){ + Program.editor.session.addDynamicMarker(Program.debugLineHighlighter, false); + //@ts-ignore + Program.editor.on("guttermousedown", (ev) => { + const row = ev.getDocumentPosition().row; + // const node = this.nodes.find((node,i) =>{ + // return node.state.node.source.lineNumber == row + // }) + const nodeIndex = this.nodes.findIndex((node,i) =>{ + return node.state.node.source.lineNumber == row +1 + }) + // console.log('gutter.row:', row, 'nodeIndex:', nodeIndex) + if(nodeIndex >=0){ + this.toggleBreakpoint(nodeIndex) + } + }) + } + Program.debugLineHighlighter.setLineNo(-1, Program.editor); const seedString = emodel.getAttribute("seeds"); const seeds = seedString?.split(" ").map((s) => parseInt(s)); @@ -254,7 +312,7 @@ export class Model { const qsSeed = parseInt(qs.get("seed")); this._seed = isNaN(qsSeed) - ? seeds?.[0] || Program.meta.int32() + ? seeds?.[0] || Program.meta.next() : qsSeed; }); @@ -310,6 +368,11 @@ export class Model { return this._seed; } + @action + public set_seed(seed:number|string) { + this._seed = Number(seed) + } + @action public start(params?: ProgramParams) { try { @@ -357,7 +420,7 @@ export class Model { @action public randomize() { - this._seed = Program.meta.int32(); + this._seed = Program.meta.next(); } private scaleTime(t: number) { @@ -470,6 +533,8 @@ export class Model { } console.log(`Time: ${this._timer.toFixed(2)}ms`); + console.log(`Steps(maybe): ${this.rendered} ${state.length}`); + this.rendered = 0; } else { if (!once) this._delay @@ -504,7 +569,7 @@ export class Model { ); for (let i = 0; i < runs; i++) { - const seed = rng_seed ? Program.meta.int32() : this._seed; + const seed = rng_seed ? Program.meta.next() : this._seed; const iter = ip?.run(seed, this._steps); const start = performance.now(); @@ -554,13 +619,18 @@ export class Model { @action public toggleBreakpoint(index: number) { const node = this.nodes[index]; + // console.log('Breakpoint: nodeIndex:',index, 'Node:',node) if (!node) return; node.breakpoint = !node.breakpoint; + const editor = Program.editor.session; + const lineNo = node.state.node.source.lineNumber -1 if (node.breakpoint) { this.breakpoints.add(node.state.node); + editor.setBreakpoint(lineNo, 'debug-breakpoint') } else { this.breakpoints.delete(node.state.node); + editor.clearBreakpoint(lineNo) } } diff --git a/src/web/right.tsx b/src/web/right.tsx index 9ba8fcc..c14ed33 100644 --- a/src/web/right.tsx +++ b/src/web/right.tsx @@ -557,21 +557,26 @@ const StateTree = observer(() => { if (xml) { const node = model.nodes[model.curr_node_index]; - Program.editor.clearSelection(); - + // Program.editor.clearSelection(); + if (node) { const source = node.state.node.source; // index starts at 1 really @xmldom - Program.editor.moveCursorTo( - source.lineNumber - 1, - source.columnNumber - 1 - ); + Program.debugLineHighlighter.setLineNo(source.lineNumber -1, Program.editor); + //@ts-ignore + // Program.editor.session._emit("changeBackMarker"); + // Program.editor.moveCursorTo( + // source.lineNumber - 1, + // source.columnNumber - 1 + // ); const lines = Program.editor.container.querySelectorAll( "div.ace_line_group" ); elem = lines[source.lineNumber] as HTMLDivElement; + } else { + Program.debugLineHighlighter.setLineNo(-1, Program.editor); } } else { elem = list.children[model.curr_node_index] as HTMLDivElement; diff --git a/src/web/style/index.css b/src/web/style/index.css index acbbf9c..7f8562d 100644 --- a/src/web/style/index.css +++ b/src/web/style/index.css @@ -240,6 +240,9 @@ input[type="text"] { border: none; transition: background-color 0.25s linear; } +input.seed { + width: 80px; +} input[type="text"]:focus { background-color: #000; @@ -334,6 +337,7 @@ input[type="color"]::-webkit-color-swatch { .node-state[data-highlight="true"] { --color: cornflowerblue !important; + background: rgba(255, 255, 0, 0.2); } .node-state[data-highlight="true"][data-breakpoint="true"] { @@ -705,3 +709,27 @@ button.danger:hover { .ace-tm .ace_marker-layer .ace_selection { background-color: #264f78; } +.debug-line { + background-color: yellow; + position: absolute; + left: 0!important; + opacity: 0.2; +} +.debug-breakpoint { + /* background-color: pink; */ + position: absolute; + /* left: 0!important; */ + /* opacity: 0.2; */ +} +.debug-breakpoint::before { + content: ''; + margin: 0; + width: 12px; + height: 12px; + background-color: crimson; + border-radius: 50%; + position: absolute; + left: 4px; + top: 1px; + cursor: pointer; +} \ No newline at end of file