-
Notifications
You must be signed in to change notification settings - Fork 53
Fill a caller's scratch list from spatial queries, and remove SparseSet #476
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
b7669f4
Optimize spatial query result reuse
Frotty 2939413
Clear retained ArrayList slots on regrow
Frotty ba73af1
Take a scratch list from the caller and drop SparseSet.
Frotty f7aedca
Fill a query result with one capacity check instead of one per element.
Frotty 6c4f4b1
Close the query snapshot before running a caller's filter.
Frotty File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,34 +1,61 @@ | ||
| package SpatialIndexForDestructables | ||
| import SparseSet | ||
| import ArrayList | ||
| import DestructableSpatialIndex | ||
| import Rect | ||
|
|
||
| /** | ||
| * Native-less Lua spatial queries for destructables. | ||
| * | ||
| * Each result is owned by the caller and must be destroyed. The range query intentionally matches | ||
| * Pass a reusable ArrayList<destructable> and the query fills it. The target is reset before it is | ||
| * populated, so one scratch list serves every call. To narrow what comes back, pass a filter rather | ||
| * than collecting everything and testing membership afterwards. | ||
| * Filters are caller-owned, and nested queries must use separate result collections. | ||
| * The range query intentionally matches | ||
| * ClosureForGroups.forDestructablesInRange: it returns the square that encloses the circle rather | ||
| * than applying a second distance test. Runtime-created destructables must be registered explicitly. | ||
| */ | ||
|
|
||
| function newDestructableResult() returns SparseSet<destructable> | ||
| return new SparseSet<destructable>(DESTRUCTABLE_SPARSE_SET_KEY) | ||
| // Caller-owned predicate evaluated once for each spatial match before it enters the result. | ||
| public interface DestructableSpatialFilter | ||
| function matches(destructable whichDestructable) returns boolean | ||
|
|
||
| public function destructablesInRect(rect area) returns SparseSet<destructable> | ||
| let result = newDestructableResult() | ||
| function appendMatches(ArrayList<destructable> result, int matched, DestructableSpatialFilter filter) | ||
| // Copy the snapshot out and close it before any caller code runs. A filter is caller code: it | ||
| // can fail, and it can yield. Failing leaves the snapshot open for good, along with the unit | ||
| // handles it holds; yielding lets another query overlap this one, and the two then read and | ||
| // close each other's snapshot. | ||
| let firstNew = result.size() | ||
| result.reserve(firstNew + matched) | ||
| for i = 0 to matched - 1 | ||
| result.unsafeAdd(destructableSpatialIndexQuery(i)) | ||
| destructableSpatialIndexEndQuery() | ||
|
|
||
| if filter != null | ||
| // Compact in place, so filtering needs no second collection. Nested queries already have | ||
| // to use separate result lists, which is what makes writing back into this one safe. | ||
| var kept = firstNew | ||
| for i = firstNew to result.size() - 1 | ||
| let match = result.get(i) | ||
| if filter.matches(match) | ||
| result.set(kept, match) | ||
| kept++ | ||
| result.truncate(kept) | ||
|
|
||
| public function destructablesInRect(ArrayList<destructable> result, rect area, | ||
| DestructableSpatialFilter filter) | ||
| result.reset() | ||
| if isLua and USE_DESTRUCTABLE_SPATIAL_INDEX | ||
| let matched = destructableSpatialIndexBeginBoxQuery( | ||
| vec2(area.getMinX(), area.getMinY()), vec2(area.getMaxX(), area.getMaxY())) | ||
| for i = 0 to matched - 1 | ||
| result.add(destructableSpatialIndexQuery(i)) | ||
| destructableSpatialIndexEndQuery() | ||
| return result | ||
|
|
||
| public function destructablesInRange(vec2 center, real range) returns SparseSet<destructable> | ||
| let result = newDestructableResult() | ||
| appendMatches(result, destructableSpatialIndexBeginBoxQuery( | ||
| vec2(area.getMinX(), area.getMinY()), vec2(area.getMaxX(), area.getMaxY())), filter) | ||
|
|
||
| public function destructablesInRect(ArrayList<destructable> result, rect area) | ||
| destructablesInRect(result, area, null) | ||
|
|
||
| public function destructablesInRange(ArrayList<destructable> result, vec2 center, real range, | ||
| DestructableSpatialFilter filter) | ||
| result.reset() | ||
| if isLua and USE_DESTRUCTABLE_SPATIAL_INDEX | ||
| let matched = destructableSpatialIndexBeginRangeQuery(center, range) | ||
| for i = 0 to matched - 1 | ||
| result.add(destructableSpatialIndexQuery(i)) | ||
| destructableSpatialIndexEndQuery() | ||
| return result | ||
| appendMatches(result, destructableSpatialIndexBeginRangeQuery(center, range), filter) | ||
|
|
||
| public function destructablesInRange(ArrayList<destructable> result, vec2 center, real range) | ||
| destructablesInRange(result, center, range, null) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,59 +1,98 @@ | ||
| package SpatialIndexForUnits | ||
| import SparseSet | ||
| import ArrayList | ||
| import UnitSpatialIndex | ||
| import Rect | ||
|
|
||
| /** | ||
| * Native-less Lua spatial queries. | ||
| * | ||
| * Each call returns an owned SparseSet<unit>. The result is independent of Warcraft groups and must | ||
| * be destroyed by the caller. These APIs are intentionally Lua-oriented: on Jass or while disabled, | ||
| * they return an empty set because no native fallback is hidden behind the generic result type. | ||
| * Pass a reusable ArrayList<unit> and the query fills it. The target is reset before it is | ||
| * populated, so one scratch list serves every call on a hot path. No Warcraft group, result | ||
| * collection, or filter is allocated or destroyed by these overloads. | ||
| * | ||
| * A query result is something you walk, so a list is the shape it wants. To narrow what comes back, | ||
| * pass a UnitSpatialFilter rather than collecting everything and testing membership afterwards - | ||
| * the filter runs once per match, before the result is built. | ||
| * | ||
| * Filters are caller-owned so one instance can be reused across queries. A capturing lambda creates | ||
| * a closure at its declaration site; keep it outside hot loops when allocation matters. | ||
| * Nested queries must use separate result collections because each call resets its target. | ||
| * | ||
| * These APIs are intentionally Lua-oriented: on Jass or while disabled, the target remains empty | ||
| * because no native fallback is hidden behind the generic result type. | ||
| */ | ||
|
|
||
| function newUnitResult() returns SparseSet<unit> | ||
| return new SparseSet<unit>(UNIT_SPARSE_SET_KEY) | ||
| // Caller-owned predicate evaluated once for each spatial match before it enters the result. | ||
| public interface UnitSpatialFilter | ||
| function matches(unit whichUnit) returns boolean | ||
|
|
||
| function addRangeMatches(SparseSet<unit> result, vec2 center, real radius, boolean collisionFiltering) | ||
| let matched = spatialIndexBeginQuery(center, radius, collisionFiltering) | ||
| function appendMatches(ArrayList<unit> result, int matched, UnitSpatialFilter filter) | ||
| // Copy the snapshot out and close it before any caller code runs. A filter is caller code: it | ||
| // can fail, and it can yield. Failing leaves the snapshot open for good, along with the unit | ||
| // handles it holds; yielding lets another query overlap this one, and the two then read and | ||
| // close each other's snapshot. | ||
| let firstNew = result.size() | ||
| result.reserve(firstNew + matched) | ||
| for i = 0 to matched - 1 | ||
| result.add(spatialIndexQueryUnit(i)) | ||
| result.unsafeAdd(spatialIndexQueryUnit(i)) | ||
| spatialIndexEndQuery() | ||
|
|
||
| /** Returns units whose origins are within radius of center. */ | ||
| public function unitsInRange(vec2 center, real radius) returns SparseSet<unit> | ||
| return unitsInRange(center, radius, false) | ||
| if filter != null | ||
| // Compact in place, so filtering needs no second collection. Nested queries already have | ||
| // to use separate result lists, which is what makes writing back into this one safe. | ||
| var kept = firstNew | ||
| for i = firstNew to result.size() - 1 | ||
| let match = result.get(i) | ||
| if filter.matches(match) | ||
| result.set(kept, match) | ||
| kept++ | ||
| result.truncate(kept) | ||
|
|
||
| /** Replaces result with units whose origins are within radius of center. */ | ||
| public function unitsInRange(ArrayList<unit> result, vec2 center, real radius) | ||
| unitsInRange(result, center, radius, false, null) | ||
|
Frotty marked this conversation as resolved.
|
||
|
|
||
| /** Replaces result with filtered units whose origins are within radius of center. */ | ||
| public function unitsInRange(ArrayList<unit> result, vec2 center, real radius, | ||
| UnitSpatialFilter filter) | ||
| unitsInRange(result, center, radius, false, filter) | ||
|
|
||
| /** Returns units in range, optionally applying collision-size filtering. */ | ||
| public function unitsInRange(vec2 center, real radius, boolean collisionFiltering) returns SparseSet<unit> | ||
| let result = newUnitResult() | ||
| /** Replaces result with units in range, optionally applying collision-size filtering. */ | ||
| public function unitsInRange(ArrayList<unit> result, vec2 center, real radius, | ||
| boolean collisionFiltering) | ||
| unitsInRange(result, center, radius, collisionFiltering, null) | ||
|
|
||
| /** Replaces result with filtered units in range, optionally applying collision-size filtering. */ | ||
| public function unitsInRange(ArrayList<unit> result, vec2 center, real radius, | ||
| boolean collisionFiltering, UnitSpatialFilter filter) | ||
| result.reset() | ||
| if isLua and USE_UNIT_SPATIAL_INDEX | ||
| addRangeMatches(result, center, radius, collisionFiltering) | ||
| return result | ||
| appendMatches(result, spatialIndexBeginQuery(center, radius, collisionFiltering), filter) | ||
|
|
||
| /** Returns units whose origins are inside the axis-aligned box. */ | ||
| public function unitsInBox(vec2 boxMin, vec2 boxMax) returns SparseSet<unit> | ||
| let result = newUnitResult() | ||
| /** Replaces result with filtered units whose origins are inside the axis-aligned box. */ | ||
| public function unitsInBox(ArrayList<unit> result, vec2 boxMin, vec2 boxMax, | ||
| UnitSpatialFilter filter) | ||
| result.reset() | ||
| if isLua and USE_UNIT_SPATIAL_INDEX | ||
| let matched = spatialIndexBeginBoxQuery(boxMin, boxMax) | ||
| for i = 0 to matched - 1 | ||
| result.add(spatialIndexQueryUnit(i)) | ||
| spatialIndexEndQuery() | ||
| return result | ||
|
|
||
| /** Returns units matching GroupEnumUnitsInRect semantics for the given rect. */ | ||
| public function unitsInRect(rect area) returns SparseSet<unit> | ||
| appendMatches(result, spatialIndexBeginBoxQuery(boxMin, boxMax), filter) | ||
|
|
||
| public function unitsInBox(ArrayList<unit> result, vec2 boxMin, vec2 boxMax) | ||
| unitsInBox(result, boxMin, boxMax, null) | ||
|
|
||
| public function unitsInRect(ArrayList<unit> result, rect area, UnitSpatialFilter filter) | ||
| // Warcraft's native unit rect enum starts 32 units above the requested minimum edge. | ||
| return unitsInBox(vec2(area.getMinX() + 32., area.getMinY() + 32.), | ||
| vec2(area.getMaxX(), area.getMaxY())) | ||
| unitsInBox(result, vec2(area.getMinX() + 32., area.getMinY() + 32.), | ||
| vec2(area.getMaxX(), area.getMaxY()), filter) | ||
|
|
||
| /** Returns currently indexed units owned by owner. This is a linear registry scan; per-player | ||
| secondary sets are intentionally not maintained in this first iteration. */ | ||
| public function unitsOfPlayer(player owner) returns SparseSet<unit> | ||
| let result = newUnitResult() | ||
| public function unitsInRect(ArrayList<unit> result, rect area) | ||
| unitsInRect(result, area, null) | ||
|
|
||
| /** Replaces result with currently indexed units owned by owner. This is a linear registry scan; | ||
| per-player secondary sets are intentionally not maintained. */ | ||
| public function unitsOfPlayer(ArrayList<unit> result, player owner, UnitSpatialFilter filter) | ||
| result.reset() | ||
| if isLua and USE_UNIT_SPATIAL_INDEX | ||
| let matched = spatialIndexBeginPlayerQuery(owner) | ||
| for i = 0 to matched - 1 | ||
| result.add(spatialIndexQueryUnit(i)) | ||
| spatialIndexEndQuery() | ||
| return result | ||
| appendMatches(result, spatialIndexBeginPlayerQuery(owner), filter) | ||
|
|
||
| public function unitsOfPlayer(ArrayList<unit> result, player owner) | ||
| unitsOfPlayer(result, owner, null) | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.