From fd3146578e8d2d900f50ea0d8ea6c6c19e59a845 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=A5=BF=E9=A3=8E=E9=80=8D=E9=81=A5=E6=B8=B8?= Date: Mon, 24 Aug 2026 21:00:59 +0800 Subject: [PATCH] docs(procgen): demonstrate script-first hot reload pipeline --- docs/usr/modules/procgen.md | 52 +++++++++++++++- examples/README.md | 1 + examples/procgen-script-pipeline/README.md | 15 +++++ examples/procgen-script-pipeline/config.nut | 6 ++ examples/procgen-script-pipeline/main.nut | 68 +++++++++++++++++++++ 5 files changed, 139 insertions(+), 3 deletions(-) create mode 100644 examples/procgen-script-pipeline/README.md create mode 100644 examples/procgen-script-pipeline/config.nut create mode 100644 examples/procgen-script-pipeline/main.nut diff --git a/docs/usr/modules/procgen.md b/docs/usr/modules/procgen.md index 862ed69e..6c7a6502 100644 --- a/docs/usr/modules/procgen.md +++ b/docs/usr/modules/procgen.md @@ -17,6 +17,52 @@ local grid = gen.generate("dungeon.bsp", p); `Params` 描述 seed、尺寸和算法参数;`Grid2D` 是结果;`OutputSpec` 决定写入 TileLayer、Image 或 Texture;`Procgen` 按注册算法名执行。 +## 纯脚本 PointSet 管线 + +程序化世界编排使用普通 Squirrel 函数,不要求节点图。`PointSet` 是带位置、法线、 +旋转、缩放、密度、独立 seed 和自定义属性的 3D 采样集合。`sampleGrid`、 +`filterHeight`、`filterDensity`、`excludeRadius`、`jitterPoints` 和 `selfPrune` +均返回新的集合,不修改输入,因此中间结果可以命名、检查、复用或分支: + +```squirrel +local rootSeed = 42; +local candidates = procgen.sampleGrid(32, 20, 2.0, + procgen.deriveSeed(rootSeed, "trees"), 0.75); +local outsideRoad = procgen.excludeRadius(candidates, 20.0, 12.0, 4.0); +local trees = procgen.selfPrune(outsideRoad, 1.8); +``` + +不要让不同内容共享一个可变随机流。用 `deriveSeed(root, "trees")`、 +`deriveSeed(root, "rocks")` 为分支派生稳定 seed;修改岩石管线不会扰动树木结果。 + +### 事务式 hot reload + +命名 `ProcgenContext` 是一次完整重建的 staging 区。`publish` 会复制输出, +`commitSystem` 成功后才原子替换该系统的上次快照;`fail`、`abortSystem`、脚本异常或 +未提交的 context 都不会破坏旧结果: + +```squirrel +function rebuildForest(seed) { + local ctx = procgen.beginSystem("forest", seed); + try { + local points = procgen.sampleGrid(32, 20, 2.0, ctx.seedFor("trees"), 0.8); + local trees = procgen.selfPrune(points, 1.8); + ctx.trace("self prune", points.getCount(), trees.getCount(), 0.0); + if (!ctx.publish("trees", trees)) throw ctx.getError(); + if (!procgen.commitSystem(ctx)) throw procgen.lastError(); + } catch (error) { + ctx.fail(error.tostring()); + procgen.commitSystem(ctx); // 失败并关闭 staging;旧快照仍然有效 + } +} + +eve_reload <- function() { rebuildForest(42); }; +``` + +`getSystemOutput` 返回已提交输出的副本;`getSystemRevision` 可判断是否成功换代; +`getSystemDebugReport` 输出 seed、revision、命名阶段点数/耗时和最终输出点数。 +可运行示例见 [`examples/procgen-script-pipeline`](../../../examples/procgen-script-pipeline/README.md)。 + ## 目标导向指南 ### 生成可玩的地牢层 @@ -203,13 +249,13 @@ local mesh = gen.generateMesh("mesh.stonewall", p, gfx); // 或 mesh.fence / mes 下列方法名来自当前 Squirrel 绑定;同一模块创建的辅助对象(例如 `World`、`Body`、`Source`)的方法也列在这里。 -- `addObject()`、`addObjectAt()`、`applyToLayer()`、`autotileGrid()`、`buildMesh()`、`clearObjects()`、`fill()`、`generate()`、`generateImage()`、`generateMesh()`、`generateNormalImage()` +- `abort()`、`abortSystem()`、`add()`、`addObject()`、`addObjectAt()`、`applyToLayer()`、`autotileGrid()`、`beginSystem()`、`buildMesh()`、`clear()`、`clearObjects()`、`commitSystem()`、`deriveSeed()`、`empty()`、`excludeRadius()`、`fail()`、`fill()`、`filterDensity()`、`filterHeight()`、`generate()`、`generateImage()`、`generateMesh()`、`generateNormalImage()` - `generateTexture()`、`generateTo()`、`getAlgorithmCount()`、`getAlgorithmId()`、`getCell()`、`getDetail()`、`getFloat()`、`getHeight()`、`getInt()` - `getLayer()`、`getMeshRecipeCount()`、`getMeshRecipeId()`、`getMeta()`、`getName()`、`getObjectCount()`、`getObjectGid()`、`getObjectHeight()`、`getObjectName()`、`getObjectType()` - `getObjectWidth()`、`getObjectX()`、`getObjectY()`、`getPalette()`、`getPaletteGid()`、`getPath()`、`getSeed()`、`getString()` - `getTarget()`、`getTextureRecipeCount()`、`getTextureRecipeId()`、`getWidth()`、`gridToJson()`、`has()`、`hasAlgorithm()`、`hasMeshRecipe()`、`hasTextureRecipe()` -- `lastError()`、`newGrid()`、`newOutput()`、`newParams()`、`randomSeed()`、`resize()`、`setCell()`、`setDetail()`、`setFloat()`、`setInt()` -- `setLayer()`、`setMeta()`、`setPalette()`、`setPaletteGid()`、`setPath()`、`setSeed()`、`setSize()`、`setString()` +- `getDensity()`、`getError()`、`getFloatAttribute()`、`getNormalX()`、`getNormalY()`、`getNormalZ()`、`getOutput()`、`getOutputCount()`、`getOutputName()`、`getPointSeed()`、`getScaleX()`、`getScaleY()`、`getScaleZ()`、`getStringAttribute()`、`getSystemDebugReport()`、`getSystemOutput()`、`getSystemOutputCount()`、`getSystemOutputName()`、`getSystemRevision()`、`getSystemSeed()`、`getTraceCount()`、`getTraceInputCount()`、`getTraceMilliseconds()`、`getTraceName()`、`getTraceOutputCount()`、`getX()`、`getY()`、`getYaw()`、`getZ()`、`hasFailed()`、`hasFloatAttribute()`、`hasOutput()`、`hasStringAttribute()`、`hasSystem()`、`isActive()`、`jitterPoints()`、`lastError()`、`newGrid()`、`newOutput()`、`newParams()`、`newPointSet()`、`publish()`、`randomSeed()`、`removeSystem()`、`resize()`、`sampleGrid()`、`seedFor()`、`selfPrune()`、`setCell()`、`setDensity()`、`setDetail()`、`setFloat()`、`setFloatAttribute()`、`setInt()` +- `setLayer()`、`setMeta()`、`setNormal()`、`setPalette()`、`setPaletteGid()`、`setPath()`、`setPointSeed()`、`setPosition()`、`setScale()`、`setSeed()`、`setSize()`、`setString()`、`setStringAttribute()`、`setYaw()`、`trace()` - `setTarget()` ## 使用要点 diff --git a/examples/README.md b/examples/README.md index 311234e7..6eb7dbca 100644 --- a/examples/README.md +++ b/examples/README.md @@ -57,6 +57,7 @@ make run/-debug GAME=examples/ | 示例 | 演示能力 | |---|---| | [procgen](procgen/README.md) | 六种地图算法(BSP / Cellular / Drunkard / Maze / 地形 / WFC)+ 纹理配方 | +| [procgen-script-pipeline](procgen-script-pipeline/README.md) | 纯脚本 PointSet 组合、确定性 seed、事务式 hot reload | | [roguelike-generator](roguelike-generator/README.md) | 种子驱动的房间走廊地牢:autotile、装饰、2D ↔ 2.5D | | [tree-generator](tree-generator/README.md) | 确定性树木:Weber-Penn / 空间殖民两种骨架算法 | | [bush-generator](bush-generator/README.md) | 低多边形灌木配方(mesh.bush)与参数实时调节 | diff --git a/examples/procgen-script-pipeline/README.md b/examples/procgen-script-pipeline/README.md new file mode 100644 index 00000000..3546b944 --- /dev/null +++ b/examples/procgen-script-pipeline/README.md @@ -0,0 +1,15 @@ +# Procgen Script Pipeline + +纯 Squirrel 程序化生成示例,不使用节点图:网格采样、排除区和自裁剪通过普通函数组合, +最终 `PointSet` 在一个命名事务中原子提交。 + +```powershell +make run/win32-debug GAME=examples/procgen-script-pipeline +``` + +- 修改并保存 `main.nut`:`eve_reload` 会重建 `forest`;生成失败时保留上次结果。 +- 按 `R`:更换根 seed 并重新生成。 +- 控制台会输出每个命名阶段的输入/输出点数、系统 revision 和 seed。 + +示例目前用中央圆形排除区模拟道路或广场。未来的 Surface、Spline 和 Volume 输入可继续 +返回 `PointSet`,不需要改变脚本编排形式。 diff --git a/examples/procgen-script-pipeline/config.nut b/examples/procgen-script-pipeline/config.nut new file mode 100644 index 00000000..95e3bad0 --- /dev/null +++ b/examples/procgen-script-pipeline/config.nut @@ -0,0 +1,6 @@ +config <- { + width = 960 + height = 640 + title = "EVEngine Procgen — Script Pipeline" + hotReload = true +}; diff --git a/examples/procgen-script-pipeline/main.nut b/examples/procgen-script-pipeline/main.nut new file mode 100644 index 00000000..d42397b7 --- /dev/null +++ b/examples/procgen-script-pipeline/main.nut @@ -0,0 +1,68 @@ +// Code-first procedural pipeline. Edit this file while the example is running: +// eve_reload rebuilds a complete staging snapshot and atomically commits it. + +if (!("forestSeed" in getroottable())) forestSeed <- 42; +if (!("forestPoints" in getroottable())) forestPoints <- null; +if (!("pipelineStatus" in getroottable())) pipelineStatus <- "not generated"; + +gfx.setBackgroundColor(0.055, 0.075, 0.07, 1.0); + +function rebuildForest() { + local ctx = procgen.beginSystem("forest", forestSeed); + if (ctx == null) { + pipelineStatus = procgen.lastError(); + return; + } + + try { + local candidates = procgen.sampleGrid(28, 18, 28.0, ctx.seedFor("candidates"), 0.8); + ctx.trace("sampleGrid", 0, candidates.getCount(), 0.0); + + // This circular exclusion stands in for a future road/spline exclusion input. + local awayFromPlaza = procgen.excludeRadius(candidates, 380.0, 230.0, 105.0); + ctx.trace("exclude plaza", candidates.getCount(), awayFromPlaza.getCount(), 0.0); + + local trees = procgen.selfPrune(awayFromPlaza, 32.0); + ctx.trace("self prune", awayFromPlaza.getCount(), trees.getCount(), 0.0); + if (!ctx.publish("trees", trees)) throw ctx.getError(); + + if (!procgen.commitSystem(ctx)) throw procgen.lastError(); + forestPoints = procgen.getSystemOutput("forest", "trees"); + pipelineStatus = procgen.getSystemDebugReport("forest"); + print("\n" + pipelineStatus + "\n"); + } catch (error) { + ctx.fail(error.tostring()); + // A failed commit closes staging but leaves the last committed forest intact. + procgen.commitSystem(ctx); + forestPoints = procgen.getSystemOutput("forest", "trees"); + pipelineStatus = "rebuild failed, previous snapshot kept: " + error.tostring(); + } +} + +eve_init <- function() { + rebuildForest(); +}; + +eve_reload <- function() { + rebuildForest(); +}; + +eve_update <- function(dt) { + if (key_just_pressed("R")) { + forestSeed += 1; + rebuildForest(); + } +}; + +eve_render <- function() { + gfx.clear(); + gfx.drawSolidRect(380.0 - 105.0, 230.0 - 105.0, 210.0, 210.0, 0.12, 0.15, 0.14, 1.0); + if (forestPoints == null) return; + for (local i = 0; i < forestPoints.getCount(); i += 1) { + local x = 70.0 + forestPoints.getX(i); + local y = 70.0 + forestPoints.getZ(i); + local size = 5.0 + (forestPoints.getPointSeed(i) % 5); + gfx.drawSolidRect(x - size * 0.5, y - size * 0.5, size, size, + 0.18, 0.48, 0.25, 1.0); + } +};