Skip to content

perf(serverareas): finish ServerWater chain sweep — SaveArea + ServerUnloadArea#272

Merged
CoreyRDean merged 1 commit into
developfrom
refactor/serverwater-chain-finish-sweep
May 26, 2026
Merged

perf(serverareas): finish ServerWater chain sweep — SaveArea + ServerUnloadArea#272
CoreyRDean merged 1 commit into
developfrom
refactor/serverwater-chain-finish-sweep

Conversation

@CoreyRDean
Copy link
Copy Markdown
Collaborator

Summary

PR #270 introduced the per-Area ServerWater linked-list (Area\FirstWater + ServerWater\NextWater) and converted the per-tick underwater-damage check in GameServer.bb to walk it, leaving the SaveArea / ServerUnloadArea sites as a deferred follow-up. This PR finishes that sweep.

Three sibling sites in src/Modules/ServerAreas.bb still iterated the global For W.ServerWater = Each ServerWater collection and filtered by If W\Area = A. After this change, no per-frame or per-save code path scans the global water collection to find an Area's subset.

Non-technical

The server keeps a list of every body of water in the world. Until PR #270 every per-frame underwater check scanned that whole list and discarded the records that belonged to a different area. PR #270 fixed the per-frame check; this PR finishes the job for the area-save and area-unload paths, so neither of those has to scan every water in the world either.

Technical changes

src/Modules/ServerAreas.bb only — one file, three sites:

  1. ServerUnloadArea (1 walk) — replaces a global after-cursor walk filtering by Area with a direct chain walk over A\FirstWater. Body still Deletes the current node (legacy module — no GC), so WNext = W\NextWater is captured before Delete W. Sets A\FirstWater = Null after the loop for safety even though A is Deleted immediately afterward.

  2. SaveArea count walk — chain walk replaces the global walk + filter. O(global_waters) → O(this_area_waters).

  3. SaveArea write walk — chain walk replaces the global walk + filter. Same complexity win. Output ordering on disk may differ (head-insert is LIFO relative to file order), but ServerLoadArea reads N records and is order-insensitive, so the round-trip behavior is identical.

Also updates a now-stale doc comment in ServerLoadArea — the line "SaveArea path still walks Each ServerWater" became false.

Acceptance criteria

  • Zero For W.ServerWater = Each ServerWater walks remain in ServerAreas.bb (grep-verified).
  • All four engine targets compile clean (Server, Client, GUE, Project Manager).
  • All Tools compile clean.
  • All 19 tests pass.
  • The global Each ServerWater collection still exists (allocation + Delete sites use it).
  • Round-trip semantics preserved (SaveArea writes count + N records; ServerLoadArea reads N records into the chain).

Test plan

  • compile.bat clean (full, including Tools)
  • test.bat — 19/19 pass
  • grep verification: no Each ServerWater walks in ServerAreas.bb

Focused unit test was infeasible without substantial inline-stubbing of the Area type's dependencies (AreaInstance, server globals). Strongest available evidence: static (compile validates Field references; grep proves removal) plus mechanically-identical pattern reference to GameServer.bb:736-758 shipped in PR #270.

Trade-offs / deferred follow-ups

  • File-on-disk water-record ordering changes (head-insert LIFO vs. file-order). Read path is order-insensitive so round-trip is preserved; only matters if someone diffs raw .dat files between pre-PR and post-PR saves. Acceptable.
  • The global For Each ServerWater collection is still iterated at allocation (New ServerWater registers; Delete unregisters). Removing the global collection entirely would be a follow-up: it would require routing every ServerWater lifecycle event through A\FirstWater, which currently has no value (no per-water lookups exist outside the chain-walking sites covered here and in perf(serverwater): O(actors*global_waters) → O(actors*per_area_waters) in per-tick underwater check #270).
  • Other deferred items from the loop's backlog still on the table: P_ChangePassword throttle (1-liner), Gubbin Tool pre-loop DeltaTime seed (1-liner), remaining 15 stub docs/modules/*.md files.

🤖 Generated with Claude Code

…rverUnloadArea

PR #270 introduced Area\FirstWater + ServerWater\NextWater linked-list
infrastructure and converted the per-tick underwater-damage check in
GameServer.bb to walk it. SaveArea and ServerUnloadArea were left
deferred for a follow-up — both still iterated the global
`For W.ServerWater = Each ServerWater` collection and filtered by
`If W\Area = A`, scanning every water in the world to find the subset
owned by one Area.

This change converts the three remaining sites:

* ServerUnloadArea (1 walk): replaces a global after-cursor walk that
  filtered by Area with a direct chain walk over A\FirstWater. The
  body still Deletes the current node (legacy module — no GC), so the
  next link is captured into WNext before Delete. Adds an explicit
  A\FirstWater = Null after the loop for safety, even though A is
  Deleted immediately afterward.

* SaveArea (2 walks): replaces a count walk + write walk pair, each
  scanning the whole global collection. The new code walks A\FirstWater
  twice in succession (count, then write). Chain head-insert order in
  ServerLoadArea is reverse of file order; ServerLoadArea reads N
  records and does not depend on order, so the file contents on disk
  may legitimately differ — but the round-trip behavior is identical.

Performance: O(global_waters) → O(this_area_waters) per call. SaveArea
is called from the area-save path (admin / shutdown), so the per-call
win is modest, but ServerUnloadArea runs whenever an Area is unloaded
and the old form scaled with the total water count across all loaded
Areas.

Also updates the now-stale doc comment on the head-insert in
ServerLoadArea — the "SaveArea path still walks Each ServerWater" line
became false.

The global `For Each ServerWater` collection still exists and is still
the source of truth for memory; this PR just stops two callsites from
using it as a discovery iterator.

Verification:
- Full compile clean (Server, Client, GUE, Project Manager, all Tools).
- All 19 tests pass.
- Grep confirms zero `For W.ServerWater = Each ServerWater` walks
  remain in ServerAreas.bb.

A focused unit test would require substantial inline stubbing of the
Area type's dependencies (AreaInstance, server globals); skipping is
documented in the PR body. Strongest available evidence: static
(compile validates Field references; grep proves removal) plus
identical-pattern reference to GameServer.bb:736-758 which shipped in
PR #270.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@CoreyRDean CoreyRDean requested a review from a team as a code owner May 26, 2026 13:10
@CoreyRDean CoreyRDean merged commit 318bc70 into develop May 26, 2026
1 check passed
@CoreyRDean CoreyRDean deleted the refactor/serverwater-chain-finish-sweep branch May 26, 2026 13:13
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1f66b1f6fa

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +393 to +394
W = A\FirstWater
While W <> Null
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Include editor-created waters when saving an area

Restricting ServerSaveArea to A\FirstWater drops valid waters that exist in the global ServerWater set but were never linked into that chain. In the editor path (src/GUE.bb:2915-2917), new waters are created with SW\Area = CurrentArea but no A\FirstWater/SW\NextWater link is established, so ZoneSave() (src/GUE.bb:9148) now writes a water count that excludes those records and they disappear on reload. Before this change, the Each ServerWater + If W\Area = A sweep preserved them.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant