Containers¶
wowlib binds the C++ std::vector members of its formats as opaque container
types — the Vector* classes on the top-level wowlib module (VectorC3Vector,
VectorSMOMaterial, …) — and the fixed-size std::array members as their
Array* counterparts (ArrayShortIntx289, ArrayC3Vectorx3; element type,
then extent). Both are handed back by reference, not copied, so:
- mutating one (element assignment; for vectors also append/clear) mutates the underlying C++ object;
- numeric containers expose a zero-copy NumPy view of their backing storage.
There is one Vector*/Array* type per (element, extent) welder found in the
welded surface; they share a uniform, list-like interface. VectorC3Vector
below is representative — an Array* is the same minus the size-changing
operations (no append/insert/resize; len is the fixed extent, and
whole-attribute assignment accepts any sequence of exactly that length).
Reference pages spell these as list[...] — they are not Python lists
Throughout the format field references (the WMO, M2, WDT and WDL
pages), a member that is really a
Vector* or Array* is displayed as list[Element] (e.g. list[C3Vector])
purely for
readability — they wrap std::vector<Element> / std::array<Element, N>, so
the spelling reads
truthfully at a glance. But these objects are not list: they are opaque
handles onto live C++ storage, not owning Python sequences. They implement a
list-like interface — indexing (v[i], v[i] = x), iteration, len(v),
membership, and (vectors only) the usual mutators (append, insert, pop,
extend,
clear) — but every operation reads or writes the underlying C++ storage directly
rather than a Python-side copy. isinstance(v, list) is False; if you need a
detached snapshot, build one explicitly with list(v).
Constructing elements in place: new()¶
Every Vector* of a struct element also carries a new() method not found on
list: it default-constructs an element in place at the end of the vector and
returns a live reference to it.
mat = wmo.root.materials.new() # a fresh, default SMOMaterial, appended in place
mat.shader = 3 # writes straight through to the C++ vector
mat.blend_mode = 1
The point is generic, import-free authoring: growing a container no longer forces
you to import the element type just to construct one and hand it to append() — the
container mints the right type for you. It exists only where it is unambiguous and
safe:
- struct elements only. A scalar vector (
VectorFloat) has nonew()— a scalar would come back by value, so writes to it would be lost; useappend(value)there. - the element must be default-constructible (all welded wire structs are).
Bulk population: reserve() and resize()¶
Two sizing methods (also absent from list) make populating a large container
cheap — and, in reserve's case, safe against the invalidation caveat below:
reserve(n)pre-allocates capacity for at leastnelements. A following run ofappend()/new()that stays within that capacity does not reallocate, so it is both faster (no repeated regrow-and-copy) and safe: element references handed out during the run stay valid, because the backing buffer never moves.resize(n)grows or shrinks to exactlynelements, value-initializing any new tail elements — the allocate-then-fill-by-index pattern. Needs a default-constructible element (all welded wire structs qualify).
v = wmo.root.materials
v.reserve(len(source)) # one allocation for the whole batch
for src in source:
m = v.new() # no reallocation ⇒ every `m` stays valid
m.shader = src.shader
reserve is available on the contiguous vectors (all of them here); resize and
reserve apply to scalar vectors too, unlike new().
Held element references are invalidated by resizing — this is undefined behavior, not an exception
A reference into one of these containers — whatever new(), v[i], or iteration
hands you — aliases the C++ vector's backing storage directly. Any operation that
resizes the vector (append/new, insert, pop, clear, assigning a
whole slice) may reallocate that storage and move every element, exactly as in
C++. References you obtained before such an operation then dangle.
Because the binding hands out a raw pointer with no liveness check, using a stale reference is undefined behavior — you may read garbage, silently corrupt unrelated memory on write, or crash the interpreter with a segfault. It is not reported as a catchable Python exception, and it will not reproduce reliably. (The container object itself is kept alive while a reference lives — it is only the buffer address that is unstable.)
Safe pattern: use the reference immediately, before growing the container.
e = v.new() # OK
e.field = 1 # OK — no resize has happened
v.new() # may reallocate: `e` is now dangling
e.field = 2 # ⚠️ undefined behavior — do not touch `e` after this
If you must keep working with an element across growth, either reserve()
enough capacity up front so the run never reallocates (see above), or re-fetch
the element by index (v[i]) after each resize.
VectorC3Vector
¶
Default constructor
Copy constructor
Construct from an iterable object
reserve
¶
Pre-allocate capacity for at least n elements (no-op if capacity already exceeds n). Prevents reallocation — and reference invalidation — across a following run of append()/new().
resize
¶
Resize to exactly n elements, value-initializing any new tail elements. Shrinking or reallocating invalidates references.