Skip to content

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 no new() — a scalar would come back by value, so writes to it would be lost; use append(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 least n elements. A following run of append()/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 exactly n elements, 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

VectorC3Vector()
VectorC3Vector(arg: list[C3Vector])
VectorC3Vector(arg: Iterable[C3Vector])

Default constructor

Copy constructor

Construct from an iterable object

clear

clear() -> None

Remove all items from list.

append

append(arg: C3Vector) -> None

Append arg to the end of the list.

insert

insert(arg0: int, arg1: C3Vector) -> None

Insert object arg1 before index arg0.

pop

pop(index: int = -1) -> C3Vector

Remove and return item at index (default last).

extend

extend(arg: list[C3Vector]) -> None

Extend self by appending elements from arg.

count

count(arg: C3Vector) -> int

Return number of occurrences of arg.

remove

remove(arg: C3Vector) -> None

Remove first occurrence of arg.

reserve

reserve(n: int) -> None

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(n: int) -> None

Resize to exactly n elements, value-initializing any new tail elements. Shrinking or reallocating invalidates references.

new

new() -> C3Vector

Default-construct a new element in place at the end and return a live reference to it.