docs(procgen): demonstrate script-first hot reload pipeline

This commit is contained in:
西风逍遥游 committed 2026-08-24 21:00:59 +08:00
1 parent d6d04d5f28
commit fd3146578e
5 files changed
+139 -3

No files matched your search

+49 -3
View File
@@ -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()`
## 使用要点
+1
View File
@@ -57,6 +57,7 @@ make run/<platform>-debug GAME=examples/<name>
| 示例 | 演示能力 |
|---|---|
| [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)与参数实时调节 |
@@ -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`,不需要改变脚本编排形式。
@@ -0,0 +1,6 @@
config <- {
width = 960
height = 640
title = "EVEngine Procgen — Script Pipeline"
hotReload = true
};
+68
View File
@@ -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);
}
};