diff --git a/.github/workflows/publish-modrinth.yml b/.github/workflows/publish-modrinth.yml index d725d33..32c0250 100644 --- a/.github/workflows/publish-modrinth.yml +++ b/.github/workflows/publish-modrinth.yml @@ -8,7 +8,7 @@ on: types: [published] permissions: - contents: read + contents: write # to attach the zip to the GitHub release jobs: publish: @@ -20,6 +20,11 @@ jobs: - name: Build the datapack zip run: git archive --format=zip -o "Timber-${{ github.event.release.tag_name }}.zip" HEAD -- data 121 pack.mcmeta pack.png LICENSE README.md + - name: Attach the zip to the GitHub release + env: + GH_TOKEN: ${{ github.token }} + run: gh release upload "${{ github.event.release.tag_name }}" "Timber-${{ github.event.release.tag_name }}.zip" --repo "$GITHUB_REPOSITORY" + - name: Read the supported Minecraft version range id: versions run: | diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8048f86..3f9dc64 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -52,6 +52,15 @@ jobs: echo "- Minecraft ${{ matrix.minecraft }}: $line" >> "$GITHUB_STEP_SUMMARY" done || true + # informational: performance counters from the benchmark test + - name: Report benchmark results + if: always() + run: | + grep -h 'TIMBER-BENCHMARK' server-output.log | sed 's/.*TIMBER-BENCHMARK //' | while read -r line; do + echo "::notice title=Benchmark (Minecraft ${{ matrix.minecraft }})::$line" + echo "- Minecraft ${{ matrix.minecraft }}: $line" >> "$GITHUB_STEP_SUMMARY" + done || true + # single check to require in the branch ruleset, so changing the matrix doesn't need a ruleset update test-result: name: Test result diff --git a/CHANGELOG.md b/CHANGELOG.md index 50b3f73..23a6635 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,5 +11,5 @@ Release notes live on the [GitHub Releases page](https://github.com/Moggla/Timbe ## Releasing 1. If the supported MC versions changed: bump `pack.mcmeta` (`min_format`/`max_format`) and match `.github/mc-version-range.txt` to it. [Pack format](https://minecraft.wiki/w/Pack_format) lists which MC version has which format number. -2. Update the version string in `data/timber/dialog/help.json`. -3. Tag the commit `vX.Y.Z` and publish it as a GitHub Release, using "Generate release notes" for the body; that triggers the Modrinth upload automatically. +2. Update the version string in `data/timber/dialog/help.json` and in `data/timber/function/settings/open/menu.mcfunction` (the Settings dialog body). +3. Tag the commit `vX.Y.Z` and publish it as a GitHub Release, using "Generate release notes" for the body; that triggers the Modrinth upload and attaches the datapack zip to the release automatically. diff --git a/data/timber/function/algorithm/fungus/leaves/search.mcfunction b/data/timber/function/algorithm/fungus/leaves/search.mcfunction index d2a0e7f..31f97c2 100644 --- a/data/timber/function/algorithm/fungus/leaves/search.mcfunction +++ b/data/timber/function/algorithm/fungus/leaves/search.mcfunction @@ -1,3 +1,4 @@ +scoreboard players add leaf_search_steps timber_bench 1 scoreboard players add leaf_distance timber 1 # search for next cap execute positioned ~ ~ ~1 unless entity @e[type=minecraft:marker,tag=timber_cap,distance=...1,sort=arbitrary,limit=1] if predicate timber:block/cap run function timber:algorithm/fungus/leaves/destroy diff --git a/data/timber/function/algorithm/tree/leaves/search.mcfunction b/data/timber/function/algorithm/tree/leaves/search.mcfunction index 13bbd5e..299eda2 100644 --- a/data/timber/function/algorithm/tree/leaves/search.mcfunction +++ b/data/timber/function/algorithm/tree/leaves/search.mcfunction @@ -1,3 +1,5 @@ +scoreboard players add leaf_search_steps timber_bench 1 + # save leaves distance tag at its position summon marker ~ ~ ~ {Tags:["timber_leaf_distance"]} scoreboard players operation @e[type=minecraft:marker,tag=timber_leaf_distance,distance=...1,sort=arbitrary,limit=1] timber = leaf_distance timber diff --git a/data/timber/function/algorithm/tree/search_2.mcfunction b/data/timber/function/algorithm/tree/search_2.mcfunction index 7f48c20..c9c5e9c 100644 --- a/data/timber/function/algorithm/tree/search_2.mcfunction +++ b/data/timber/function/algorithm/tree/search_2.mcfunction @@ -1,3 +1,5 @@ +scoreboard players add log_probes timber_bench 1 + # if log execute if predicate timber:block/log unless entity @e[type=minecraft:marker,tag=timber_log,distance=...1,sort=arbitrary,limit=1] run function timber:algorithm/tree/chop # if root diff --git a/data/timber/function/init.mcfunction b/data/timber/function/init.mcfunction index 7547d8e..312d368 100644 --- a/data/timber/function/init.mcfunction +++ b/data/timber/function/init.mcfunction @@ -13,6 +13,7 @@ scoreboard objectives add timber_menu dummy scoreboard objectives add timber_disabled dummy scoreboard objectives remove timber_prsistent scoreboard objectives add timber_persistent dummy +scoreboard objectives add timber_bench dummy scoreboard players reset * TimberSettings scoreboard players reset * TimberSetBlocks diff --git a/data/timber/function/run.mcfunction b/data/timber/function/run.mcfunction index 849ae09..8ef82c8 100644 --- a/data/timber/function/run.mcfunction +++ b/data/timber/function/run.mcfunction @@ -3,6 +3,11 @@ scoreboard players set leaves_found timber 0 scoreboard players set tree_size timber 0 scoreboard players set tree_type timber 0 +# performance counters (see #11): how many times the traversal probes for a neighboring log/root, and how many times it +# (re-)enters the leaf/cap search. A dedicated benchmark test resets and reads these to get a comparable number across changes. +scoreboard players set log_probes timber_bench 0 +scoreboard players set leaf_search_steps timber_bench 0 + # load settings to other scoreboard (leaves persistent) execute unless score persistent timber matches 1.. run scoreboard players set @s timber_persistent 0 execute if score persistent timber matches 1.. run scoreboard players set @s timber_persistent 1 diff --git a/data/timber/function/settings/open/menu.mcfunction b/data/timber/function/settings/open/menu.mcfunction index d86cda6..cc79e3e 100644 --- a/data/timber/function/settings/open/menu.mcfunction +++ b/data/timber/function/settings/open/menu.mcfunction @@ -1,6 +1,6 @@ # opens the "Settings" settings page with the current values # only reachable for admins -data modify storage timber:settings dialog set value {"type":"minecraft:multi_action","title":"Timber Settings","external_title":"Timber Settings","body":{"type":"minecraft:plain_message","contents":"Changes are saved for the whole server.","width":300},"inputs":[],"can_close_with_escape":true,"pause":false,"after_action":"none","columns":2,"actions":[],"exit_action":{"label":{"translate":"gui.back"},"width":150,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 98"}}} +data modify storage timber:settings dialog set value {"type":"minecraft:multi_action","title":"Timber Settings","external_title":"Timber Settings","body":{"type":"minecraft:plain_message","contents":["",{"text":"Version v8.0.0","color":"#724E21"},"\n\n",{"text":"Changes are saved for the whole server."}],"width":300},"inputs":[],"can_close_with_escape":true,"pause":false,"after_action":"none","columns":2,"actions":[],"exit_action":{"label":{"translate":"gui.back"},"width":150,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 98"}}} execute if score standing timber matches 1 run data modify storage timber:settings dialog.actions append value {"label":{"translate":"options.generic_value","with":[{"text":"Works When Standing"},{"translate":"options.on","color":"green"}]},"width":150,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 100"}} execute unless score standing timber matches 1 run data modify storage timber:settings dialog.actions append value {"label":{"translate":"options.generic_value","with":[{"text":"Works When Standing"},{"translate":"options.off","color":"red"}]},"width":150,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 100"}} execute if score sneaking timber matches 1 run data modify storage timber:settings dialog.actions append value {"label":{"translate":"options.generic_value","with":[{"text":"Works When Sneaking"},{"translate":"options.on","color":"green"}]},"width":150,"action":{"type":"minecraft:run_command","command":"trigger TimberSettings set 101"}} diff --git a/data/timber/function/uninstall.mcfunction b/data/timber/function/uninstall.mcfunction index c73dbcf..4abe3f4 100644 --- a/data/timber/function/uninstall.mcfunction +++ b/data/timber/function/uninstall.mcfunction @@ -27,6 +27,7 @@ scoreboard objectives remove TimberSetLeaves scoreboard objectives remove timber_menu scoreboard objectives remove timber_disabled scoreboard objectives remove timber_persistent +scoreboard objectives remove timber_bench # remove markers kill @e[type=minecraft:marker,tag=timber_slow_chop] diff --git a/tests/data/timber_test/function/report_benchmark.mcfunction b/tests/data/timber_test/function/report_benchmark.mcfunction new file mode 100644 index 0000000..82d4b8c --- /dev/null +++ b/tests/data/timber_test/function/report_benchmark.mcfunction @@ -0,0 +1,7 @@ +#> Prints the performance counters of the chop that just ran (see run.mcfunction's "performance counters" reset). Same two paths as +#> the warn_* helpers (log line for the CI, chat for players). Not a warning: nothing here ever fails the test. +#> @param test name of the test +#> @param log_probes value of "log_probes timber_bench" after the chop +#> @param leaf_search_steps value of "leaf_search_steps timber_bench" after the chop +$execute unless entity @a[tag=!timber_test_dummy] run say TIMBER-BENCHMARK $(test): log_probes=$(log_probes) leaf_search_steps=$(leaf_search_steps) +$tellraw @a[tag=!timber_test_dummy] {"text":"Benchmark $(test): log_probes=$(log_probes) leaf_search_steps=$(leaf_search_steps)","color":"aqua"} diff --git a/tests/data/timber_test/test/benchmark_tree_shapes.mcfunction b/tests/data/timber_test/test/benchmark_tree_shapes.mcfunction new file mode 100644 index 0000000..3addcdf --- /dev/null +++ b/tests/data/timber_test/test/benchmark_tree_shapes.mcfunction @@ -0,0 +1,116 @@ +#> Not a correctness test: reports the performance counters (see #11) for four deterministic, hand-built shapes, so the +#> numbers are exactly reproducible across runs and comparable before/after an algorithm change: a small baseline tree, +#> then three ~38-log stress shapes spread long horizontally, straight up, and branching in every direction, to see +#> whether a fix helps all shapes equally or just one. Runs alone (its own environment): the counters are shared +#> datapack-wide and would be corrupted by another chop happening at the same time. +#> Every leaf is placed directly face-adjacent to a log (a "+" cross around each log run, no diagonal corners): the leaf +#> search only steps to a strictly farther "distance" than where it came from, so a hand-built same-distance leaf that +#> isn't itself touching a log needs a real, increasing distance gradient to be reachable, which it doesn't have. +# @environment timber_test:benchmark +# @template timber_test:arena +# @timeout 400 +# @skyaccess true + +function timber_test:platform +await delay 1s +function timber_test:player {name:"bench_t",axe:"minecraft:netherite_axe"} + +# --- small: a normal-sized hand-built oak, the baseline everything else is compared against +fill ~19 ~1 ~20 ~19 ~4 ~20 minecraft:oak_leaves[persistent=false,distance=1] +fill ~21 ~1 ~20 ~21 ~4 ~20 minecraft:oak_leaves[persistent=false,distance=1] +fill ~20 ~1 ~19 ~20 ~4 ~19 minecraft:oak_leaves[persistent=false,distance=1] +fill ~20 ~1 ~21 ~20 ~4 ~21 minecraft:oak_leaves[persistent=false,distance=1] +setblock ~20 ~5 ~20 minecraft:oak_leaves[persistent=false,distance=1] +fill ~20 ~1 ~20 ~20 ~4 ~20 minecraft:oak_log[axis=y] +assert block ~20 ~1 ~20 minecraft:oak_log +dummy bench_t mine ~20 ~1 ~20 +await not block ~20 ~1 ~20 #minecraft:logs +await delay 5t +await not entity @e[type=minecraft:marker,dx=39,dy=39,dz=39] +scoreboard objectives add timber_test dummy +data modify storage timber_test:tmp test set value "benchmark_small" +execute store result storage timber_test:tmp log_probes int 1 run scoreboard players get log_probes timber_bench +execute store result storage timber_test:tmp leaf_search_steps int 1 run scoreboard players get leaf_search_steps timber_bench +function timber_test:report_benchmark with storage timber_test:tmp +function timber_test:assert_no_tree {test:"benchmark_small"} + +# --- long horizontal: a 37 log line along x, mined at its middle (the initial chop detection only looks for the dropped +# item within 7 blocks of whoever mined, so this can't be mined from far away like the tall/bushy shapes mine their base) +fill ~2 ~2 ~20 ~38 ~2 ~20 minecraft:oak_leaves[persistent=false,distance=1] +fill ~2 ~0 ~20 ~38 ~0 ~20 minecraft:oak_leaves[persistent=false,distance=1] +fill ~2 ~1 ~19 ~38 ~1 ~19 minecraft:oak_leaves[persistent=false,distance=1] +fill ~2 ~1 ~21 ~38 ~1 ~21 minecraft:oak_leaves[persistent=false,distance=1] +setblock ~1 ~1 ~20 minecraft:oak_leaves[persistent=false,distance=1] +setblock ~39 ~1 ~20 minecraft:oak_leaves[persistent=false,distance=1] +fill ~2 ~1 ~20 ~38 ~1 ~20 minecraft:oak_log[axis=x] +assert block ~20 ~1 ~20 minecraft:oak_log +dummy bench_t mine ~20 ~1 ~20 +await not block ~20 ~1 ~20 #minecraft:logs +await delay 5t +await not entity @e[type=minecraft:marker,dx=39,dy=39,dz=39] +data modify storage timber_test:tmp test set value "benchmark_long_horizontal" +execute store result storage timber_test:tmp log_probes int 1 run scoreboard players get log_probes timber_bench +execute store result storage timber_test:tmp leaf_search_steps int 1 run scoreboard players get leaf_search_steps timber_bench +function timber_test:report_benchmark with storage timber_test:tmp +function timber_test:assert_no_tree {test:"benchmark_long_horizontal"} + +# --- tall: a 37 log column straight up, mined at its base (close to where the dummy spawns) +fill ~19 ~1 ~20 ~19 ~37 ~20 minecraft:oak_leaves[persistent=false,distance=1] +fill ~21 ~1 ~20 ~21 ~37 ~20 minecraft:oak_leaves[persistent=false,distance=1] +fill ~20 ~1 ~19 ~20 ~37 ~19 minecraft:oak_leaves[persistent=false,distance=1] +fill ~20 ~1 ~21 ~20 ~37 ~21 minecraft:oak_leaves[persistent=false,distance=1] +setblock ~20 ~38 ~20 minecraft:oak_leaves[persistent=false,distance=1] +fill ~20 ~1 ~20 ~20 ~37 ~20 minecraft:oak_log[axis=y] +assert block ~20 ~1 ~20 minecraft:oak_log +dummy bench_t mine ~20 ~1 ~20 +await not block ~20 ~1 ~20 #minecraft:logs +await delay 5t +await not entity @e[type=minecraft:marker,dx=39,dy=39,dz=39] +data modify storage timber_test:tmp test set value "benchmark_tall" +execute store result storage timber_test:tmp log_probes int 1 run scoreboard players get log_probes timber_bench +execute store result storage timber_test:tmp leaf_search_steps int 1 run scoreboard players get leaf_search_steps timber_bench +function timber_test:report_benchmark with storage timber_test:tmp +function timber_test:assert_no_tree {test:"benchmark_tall"} + +# --- bushy: a 10 tall trunk with a branch reaching out in +x, -x, +z and -z at half height, ~38 logs total, mined at the +# base. Each run (trunk, each branch) gets its own "+" cross of leaves around it, same as the small/tall trunks above. +fill ~19 ~1 ~20 ~19 ~10 ~20 minecraft:oak_leaves[persistent=false,distance=1] +fill ~21 ~1 ~20 ~21 ~10 ~20 minecraft:oak_leaves[persistent=false,distance=1] +fill ~20 ~1 ~19 ~20 ~10 ~19 minecraft:oak_leaves[persistent=false,distance=1] +fill ~20 ~1 ~21 ~20 ~10 ~21 minecraft:oak_leaves[persistent=false,distance=1] +setblock ~20 ~11 ~20 minecraft:oak_leaves[persistent=false,distance=1] +fill ~21 ~6 ~20 ~27 ~6 ~20 minecraft:oak_leaves[persistent=false,distance=1] +fill ~21 ~4 ~20 ~27 ~4 ~20 minecraft:oak_leaves[persistent=false,distance=1] +fill ~21 ~5 ~19 ~27 ~5 ~19 minecraft:oak_leaves[persistent=false,distance=1] +fill ~21 ~5 ~21 ~27 ~5 ~21 minecraft:oak_leaves[persistent=false,distance=1] +setblock ~28 ~5 ~20 minecraft:oak_leaves[persistent=false,distance=1] +fill ~13 ~6 ~20 ~19 ~6 ~20 minecraft:oak_leaves[persistent=false,distance=1] +fill ~13 ~4 ~20 ~19 ~4 ~20 minecraft:oak_leaves[persistent=false,distance=1] +fill ~13 ~5 ~19 ~19 ~5 ~19 minecraft:oak_leaves[persistent=false,distance=1] +fill ~13 ~5 ~21 ~19 ~5 ~21 minecraft:oak_leaves[persistent=false,distance=1] +setblock ~12 ~5 ~20 minecraft:oak_leaves[persistent=false,distance=1] +fill ~20 ~6 ~21 ~20 ~6 ~27 minecraft:oak_leaves[persistent=false,distance=1] +fill ~20 ~4 ~21 ~20 ~4 ~27 minecraft:oak_leaves[persistent=false,distance=1] +fill ~19 ~5 ~21 ~19 ~5 ~27 minecraft:oak_leaves[persistent=false,distance=1] +fill ~21 ~5 ~21 ~21 ~5 ~27 minecraft:oak_leaves[persistent=false,distance=1] +setblock ~20 ~5 ~28 minecraft:oak_leaves[persistent=false,distance=1] +fill ~20 ~6 ~13 ~20 ~6 ~19 minecraft:oak_leaves[persistent=false,distance=1] +fill ~20 ~4 ~13 ~20 ~4 ~19 minecraft:oak_leaves[persistent=false,distance=1] +fill ~19 ~5 ~13 ~19 ~5 ~19 minecraft:oak_leaves[persistent=false,distance=1] +fill ~21 ~5 ~13 ~21 ~5 ~19 minecraft:oak_leaves[persistent=false,distance=1] +setblock ~20 ~5 ~12 minecraft:oak_leaves[persistent=false,distance=1] +fill ~20 ~1 ~20 ~20 ~10 ~20 minecraft:oak_log[axis=y] +fill ~21 ~5 ~20 ~27 ~5 ~20 minecraft:oak_log[axis=x] +fill ~13 ~5 ~20 ~19 ~5 ~20 minecraft:oak_log[axis=x] +fill ~20 ~5 ~21 ~20 ~5 ~27 minecraft:oak_log[axis=z] +fill ~20 ~5 ~13 ~20 ~5 ~19 minecraft:oak_log[axis=z] +assert block ~20 ~1 ~20 minecraft:oak_log +dummy bench_t mine ~20 ~1 ~20 +await not block ~20 ~1 ~20 #minecraft:logs +await delay 5t +await not entity @e[type=minecraft:marker,dx=39,dy=39,dz=39] +data modify storage timber_test:tmp test set value "benchmark_bushy" +execute store result storage timber_test:tmp log_probes int 1 run scoreboard players get log_probes timber_bench +execute store result storage timber_test:tmp leaf_search_steps int 1 run scoreboard players get leaf_search_steps timber_bench +function timber_test:report_benchmark with storage timber_test:tmp +function timber_test:assert_no_tree {test:"benchmark_bushy"} diff --git a/tests/data/timber_test/test/settings_plant_sapling_on_decreases_the_stack.mcfunction b/tests/data/timber_test/test/settings_plant_sapling_on_decreases_the_stack.mcfunction new file mode 100644 index 0000000..9dab24d --- /dev/null +++ b/tests/data/timber_test/test/settings_plant_sapling_on_decreases_the_stack.mcfunction @@ -0,0 +1,12 @@ +#> With plant_sapling on, planting a sapling from a stack of more than one plants it and removes just one from the stack (the loop checks every 2 seconds) +# @environment timber_test:plant_sapling_on +# @template timber_test:arena +# @timeout 400 +# @skyaccess true + +function timber_test:platform +summon minecraft:item ~10.5 ~0.5 ~10.5 {Item:{id:"minecraft:oak_sapling",count:5}} + +# the sapling item falls on the dirt, the loop runs every 2 seconds +await block ~10 ~ ~10 minecraft:oak_sapling +assert entity @e[type=minecraft:item,dx=39,dy=39,dz=39,nbt={Item:{id:"minecraft:oak_sapling",count:4}}] diff --git a/tests/data/timber_test/test/settings_plant_sapling_on_plants_the_sapling.mcfunction b/tests/data/timber_test/test/settings_plant_sapling_on_plants_the_sapling.mcfunction index daf0b2d..278be3a 100644 --- a/tests/data/timber_test/test/settings_plant_sapling_on_plants_the_sapling.mcfunction +++ b/tests/data/timber_test/test/settings_plant_sapling_on_plants_the_sapling.mcfunction @@ -1,4 +1,4 @@ -#> With plant_sapling on a sapling item that lies on the ground is planted (the loop checks every 2 seconds) +#> With plant_sapling on, planting the last sapling of a stack plants it and removes the item entity entirely (the loop checks every 2 seconds) # @environment timber_test:plant_sapling_on # @template timber_test:arena # @timeout 400 @@ -9,3 +9,4 @@ summon minecraft:item ~10.5 ~0.5 ~10.5 {Item:{id:"minecraft:oak_sapling",count:1 # the sapling item falls on the dirt, the loop runs every 2 seconds await block ~10 ~ ~10 minecraft:oak_sapling +assert not entity @e[type=minecraft:item,dx=39,dy=39,dz=39,nbt={Item:{id:"minecraft:oak_sapling"}}] diff --git a/tests/data/timber_test/test_environment/benchmark.json b/tests/data/timber_test/test_environment/benchmark.json new file mode 100644 index 0000000..c1a66db --- /dev/null +++ b/tests/data/timber_test/test_environment/benchmark.json @@ -0,0 +1,5 @@ +{ + "type": "minecraft:function", + "setup": "timber_test:env/group_setup", + "teardown": "timber_test:env/group_teardown" +}