Class SphereSpiralIterator
CircleSpiralIterator.
Backed by a shared SphereOffsets list pre-built at the server's maximum allowed
radius. Each player iterator records its own (smaller-or-equal) radius and the upfront
binary search in init(int, int, int, int, int) clamps the cursor to the player's
sphere; everything in the resulting cursor range is guaranteed inside the player's sphere,
so advance() walks linearly without re-testing each cell.
Typical usage
// At server startup (once):
SphereOffsets shared = SphereOffsets.build(maxR);
// Per player:
SphereSpiralIterator iter = new SphereSpiralIterator(shared);
iter.configure(playerR); // also when the slider moves
// Per tick, walk only the newly-visible distance band:
iter.init(playerCx, playerCy, playerCz, lastSentDistSq + 1, currentMaxDistSq);
while (iter.advance()) {
SpherePosition p = iter.position();
// queue (p.x, p.y, p.z) for section load
}
Range semantics
- The 5-arg
init(int, int, int, int, int)restricts iteration to offsets whosedistSqlies in[startDistSq, endDistSq](inclusive both ends). endDistSqis internally clamped to the player'splayerR²; passingInteger.MAX_VALUEis therefore safe and means "walk to the player's edge".- The 3-arg
init(int, int, int)is a shortcut for[0, MAX_VALUE].
Cost
configure(int): a multiply and a volatile write; cheap, can be called whenever the player's view-radius slider changes.init(int, int, int, int, int): two binary searches into the shareddistSqarray, O(logshared.count).advance(): O(1) per emitted cell, one array triplet read and the center offset add; no containment test in the hot loop.
Thread safety
Not thread-safe. The sharedSphereOffsets is immutable and may be referenced by
many iterators concurrently, but each iterator instance holds mutable cursor and position
state and must be used from a single thread.-
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionbooleanadvance()Advances the iterator to the next cell in the current[startDistSq, endDistSq]range.voidconfigure(int playerR) Sets the player's sphere radius.booleancontainsOffset(int dx, int dy, int dz) Tests whether the given offset from the sphere center is inside the configured sphere.intReturns the largest distSq value such that every offset in this iterator's configured[startDistSq, endDistSq]range withdistSq <= returnhas been considered (yielded) since the lastinit(int, int, int)call.voidinit(int cx, int cy, int cz) Begins iteration from the origin out to the player's edge.voidinit(int cx, int cy, int cz, int startDistSq, int endDistSq) Begins iteration over offsets in the player's sphere whosedistSqlies in[startDistSq, endDistSq].position()Returns the position written by the most recent successfuladvance()call.
-
Constructor Details
-
SphereSpiralIterator
- Parameters:
shared- the pre-built offset list; must outlive the iterator. Typically the server-wide instance built from the configured maximum radius.
-
-
Method Details
-
configure
public void configure(int playerR) Sets the player's sphere radius. Must be called at least once beforeinit(int, int, int)and may be called again whenever the player's view-radius slider changes; no allocations.- Parameters:
playerR- radius,0 <= playerR <= shared.maxR- Throws:
IllegalArgumentException- ifplayerRis negative or exceeds the shared maximum
-
init
public void init(int cx, int cy, int cz) Begins iteration from the origin out to the player's edge. Equivalent toinit(cx, cy, cz, 0, Integer.MAX_VALUE). -
init
public void init(int cx, int cy, int cz, int startDistSq, int endDistSq) Begins iteration over offsets in the player's sphere whosedistSqlies in[startDistSq, endDistSq].endDistSqis internally clamped toplayerR².- Parameters:
cx- world-space chunk X of the centercy- world-space chunk Y of the centercz- world-space chunk Z of the centerstartDistSq- inclusive lower bound on offset distance² (use0for "from the player out")endDistSq- inclusive upper bound on offset distance² (useInteger.MAX_VALUEfor "to the player's edge")
-
containsOffset
public boolean containsOffset(int dx, int dy, int dz) Tests whether the given offset from the sphere center is inside the configured sphere.Uses the radius set by
configure(int); independent ofinit(int, int, int)state. Provided so unload checks elsewhere can use the exact same predicate the iterator uses for load. Keeping load and unload as a single source of truth avoids any shape mismatch.- Parameters:
dx- horizontal X offset from centerdy- vertical Y offset from centerdz- horizontal Z offset from center- Returns:
trueif the offset is inside the configured sphere
-
advance
public boolean advance()Advances the iterator to the next cell in the current[startDistSq, endDistSq]range.No containment test in the loop: the cursor range was clamped to the player's sphere at
init, so every offset in[cursor, endCursor)is guaranteed inside.- Returns:
trueif a cell was found andposition()now holds it;falseif iteration is exhausted
-
position
Returns the position written by the most recent successfuladvance()call. The same instance is reused across calls, so copy fields out before the nextadvance()if you need them later. -
getCompletedDistSq
public int getCompletedDistSq()Returns the largest distSq value such that every offset in this iterator's configured[startDistSq, endDistSq]range withdistSq <= returnhas been considered (yielded) since the lastinit(int, int, int)call.The 3D analog of
CircleSpiralIterator.getCompletedRadius(). Suitable for storing in a tracker's "sent up to" cursor: passresult + 1asstartDistSqon the nextinitcall to resume.Conservative on partial levels: when the iterator is partway through a distSq value that has multiple offsets (e.g. the six face cells of shell 1 all sharing
distSq == 1, and only some have been visited), the level is reported as in-progress, where the return is one less than that level's distSq. A resume fromreturn + 1will re-walk the partial level, which the tracker's "already tracked" check makes cheap.Special cases:
- If the iterator was init'd with an empty range (no offsets in
[startDistSq, endDistSq]), or has been advanced to exhaustion, the configuredendDistSqis returned, meaning the range is fully processed. - If
advance()has not been called at all sinceinit, the configuredstartDistSq - 1is returned, meaning no progress yet.
- If the iterator was init'd with an empty range (no offsets in
-