Skip to content

Commit 3aad341

Browse files
committed
table: add table.count(tbl)
Backport of retail's table.count (added 11.2.5): returns (numTableNodes, numArrayNodes, maxArrayIndex) -- total entries, entries with an integer key in [1..numTableNodes], and the largest positive integral key (0 if none). Pure lua_next walk, no Lua-internal layout dependency; keys read via ToNumber only (ToString on a numeric key would corrupt traversal). All five cases verified to match retail, including negative-key handling ({[-3]=x} -> 1, 0, 0).
1 parent 3141acf commit 3aad341

3 files changed

Lines changed: 102 additions & 1 deletion

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ Full per-function reference: **[docs/API.md](docs/API.md)**.
4747
| [Loot](docs/API.md#loot) | `C_Loot.GetNearbyLootableUnits`, `C_Loot.GetLastScanResults`, `C_Loot.IsScanInProgress`, `C_Loot.LootUnit`, `C_Loot.LootUnitItem`, `C_Loot.ScanNearbyLoot` |
4848
| [LootHistory](docs/API.md#loothistory) | `C_LootHistory.GetNumItems`, `C_LootHistory.GetItem`, `C_LootHistory.GetPlayerInfo`, `C_LootHistory.Clear` |
4949
| [LossOfControl](docs/API.md#lossofcontrol) | `C_LossOfControl.GetActiveLossOfControlData`, `C_LossOfControl.GetActiveLossOfControlDataCount` |
50-
| [Lua](docs/API.md#lua) | `coroutine.create`, `coroutine.resume`, `coroutine.status`, `coroutine.wrap`, `coroutine.yield`, `CreateFromMixins`, `math.fmod`, `math.huge`, `math.modf`, `Mixin`, `select`, `string.gmatch`, `string.match`, `string.reverse`, `strjoin`, `strreplace`, `strrev`, `strsplit`, `strtrim`, `table.wipe` |
50+
| [Lua](docs/API.md#lua) | `coroutine.create`, `coroutine.resume`, `coroutine.status`, `coroutine.wrap`, `coroutine.yield`, `CreateFromMixins`, `math.fmod`, `math.huge`, `math.modf`, `Mixin`, `select`, `string.gmatch`, `string.match`, `string.reverse`, `strjoin`, `strreplace`, `strrev`, `strsplit`, `strtrim`, `table.count`, `table.wipe` |
5151
| [Macros](docs/API.md#macros) | `GetLooseMacroIcons`, `GetLooseMacroItemIcons`, `GetMacroIcons`, `GetMacroItemIcons`, `GetMacroSpell` |
5252
| [Mail](docs/API.md#mail) | `GetInboxItemLink`, `GetSendMailItemLink` |
5353
| [Map](docs/API.md#map) | `C_Map.GetAreaInfo`, `C_Map.GetAreas`, `C_Map.GetAreaTriggerInfo`, `C_Map.GetAreaTriggers`, `C_Map.GetBestMapForUnit`, `C_Map.GetMapAreaIDs`, `C_Map.GetMapOverlays`, `C_Map.GetMapWorldSize` |

docs/API.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,7 @@ build instructions.
300300
- [Lua](#lua)
301301
- [`select(index, ...)`](#selectindex-)
302302
- [`table.wipe(t)`](#tablewipet)
303+
- [`table.count(tbl)`](#tablecounttbl)
303304
- [`Mixin(object, ...)` / `CreateFromMixins(...)`](#mixinobject--createfrommixins)
304305
- [`string.match` / `string.gmatch`](#stringmatch--stringgmatch)
305306
- [`strsplit(sep, str [, pieces])`](#strsplitsep-str--pieces)
@@ -7215,6 +7216,29 @@ undefined per the Lua reference manual.
72157216

72167217
Errors on non-table input.
72177218

7219+
### `table.count(tbl)`
7220+
7221+
Returns `(numTableNodes, numArrayNodes, maxArrayIndex)` describing how a table
7222+
is populated (a modern WoW diagnostic, added retail 11.2.5). These are counts
7223+
of live entries, **not** the table's allocated capacity:
7224+
7225+
- **`numTableNodes`** — total number of key/value pairs.
7226+
- **`numArrayNodes`** — how many of those have an integer key in the range
7227+
`[1..numTableNodes]` (the "contiguous array part" heuristic).
7228+
- **`maxArrayIndex`** — the largest positive integral key (`>= 1`), or `0` if
7229+
there is none. Negative/zero integral keys don't count.
7230+
7231+
```lua
7232+
table.count({ 10, 20, 30 }) -- 3, 3, 3
7233+
table.count({ a = 1, b = 2 }) -- 2, 0, 0
7234+
table.count({ [1] = "x", foo = "y" }) -- 2, 1, 1
7235+
table.count({ [100] = "x" }) -- 1, 0, 100
7236+
table.count({ [-3] = "x" }) -- 1, 0, 0
7237+
```
7238+
7239+
Pure iteration over the table (`lua_next`), so no dependency on Lua's internal
7240+
storage layout. Errors on non-table input.
7241+
72187242
### `Mixin(object, ...)` / `CreateFromMixins(...)`
72197243

72207244
The FrameXML table-mixin primitives, provided as engine C functions.

src/table/Count.cpp

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// This file is part of ClassicAPI.
2+
//
3+
// ClassicAPI is free software: you can redistribute it and/or modify it under the terms
4+
// of the GNU General Public License as published by the Free Software Foundation, either
5+
// version 3 of the License, or (at your option) any later version.
6+
//
7+
// ClassicAPI is distributed in the hope that it will be useful, but WITHOUT ANY
8+
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
9+
// PURPOSE. See the GNU General Public License for more details.
10+
//
11+
// You should have received a copy of the GNU General Public License along with
12+
// ClassicAPI. If not, see <https://www.gnu.org/licenses/>.
13+
14+
#include "Game.h"
15+
16+
#include <cmath>
17+
#include <vector>
18+
19+
namespace Table::Count {
20+
21+
// `numTableNodes, numArrayNodes, maxArrayIndex = table.count(tbl)` — a modern
22+
// WoW diagnostic (added retail 11.2.5) reporting how a table is populated.
23+
// These are COUNTS of live entries, not the table's allocated capacity:
24+
//
25+
// numTableNodes total number of key/value pairs.
26+
// numArrayNodes count of pairs whose key is an integer in [1..numTableNodes]
27+
// (the "looks like a contiguous array part" heuristic).
28+
// maxArrayIndex the largest positive integral key (>= 1), or 0 if none.
29+
// Verified against retail: negative/zero integral keys do
30+
// NOT count ({[-3]=x} -> 1, 0, 0).
31+
//
32+
// Pure iteration — no Lua-internal struct layout needed. Note we only ever
33+
// read a key via ToNumber, never ToString: lua_tostring on a NUMERIC key
34+
// rewrites it to a string in place and corrupts lua_next's traversal.
35+
static int __fastcall Script_table_count(void *L) {
36+
if (Game::Lua::Type(L, 1) != Game::Lua::TYPE_TABLE) {
37+
Game::Lua::Error(L, "Usage: table.count(table)");
38+
return 0;
39+
}
40+
41+
std::vector<double> integralKeys;
42+
int total = 0;
43+
double maxKey = 0.0; // largest positive integral key seen; 0 = none
44+
45+
Game::Lua::PushNil(L);
46+
while (Game::Lua::Next(L, 1) != 0) {
47+
++total;
48+
// key at -2, value at -1.
49+
if (Game::Lua::Type(L, -2) == Game::Lua::TYPE_NUMBER) {
50+
const double k = Game::Lua::ToNumber(L, -2);
51+
if (k == std::floor(k)) { // integral key
52+
integralKeys.push_back(k);
53+
if (k >= 1.0 && k > maxKey) // only positive keys count
54+
maxKey = k;
55+
}
56+
}
57+
Game::Lua::SetTop(L, -2); // pop value, keep key for the next Next()
58+
}
59+
60+
int numArray = 0;
61+
for (double k : integralKeys)
62+
if (k >= 1.0 && k <= static_cast<double>(total))
63+
++numArray;
64+
65+
Game::Lua::PushNumber(L, static_cast<double>(total));
66+
Game::Lua::PushNumber(L, static_cast<double>(numArray));
67+
Game::Lua::PushNumber(L, maxKey);
68+
return 3;
69+
}
70+
71+
static void RegisterLuaFunctions() {
72+
Game::Lua::RegisterTableFunction("table", "count", &Script_table_count);
73+
}
74+
75+
static const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
76+
77+
} // namespace Table::Count

0 commit comments

Comments
 (0)