Skip to content

Type/Pool System

The type system provides pool-based memory allocation, property accessors, constructors/destructors, and reference validation on top of KSP's array-based data model.

Defining a Type

1. Define Members

define MyType.MEMBERS := field1, field2, field3

define NodeEnv.MEMBERS := ...
    node_id, ...
    input_type, ...
    flag.disable_paths_on_release, ...
    sentVoice

Compose member lists from multiple sources:

define Voice.MEMBERS := Voice.BASE_MEMBERS, Voice.ADD_MEMBERS

2. Provide Initial Values

declare NodeEnv.INIT[] := (...
    -1, ...     {node_id}
    0, ...      {input_type}
    TRUE, ...   {flag.disable_paths_on_release}
    -1 ...      {sentVoice}
)

3. Wrap in def() Macro

macro Entry.def()
    define Entry.MEMBERS := prev, next, data
    declare Entry.INIT[] := (-1, -1, 0)
end macro

Creating Pools

type.Create(#type#) / type.CreateCustomPool(#type#, #pool#)

type.Create(Entry)                          // Default pool: 524,288 elements
type.CreateCustomPool(NodeEnv, 1000000)     // Custom pool size

This generates:

Generated Item Description
#type#.field.* Const enumeration of field indices
#type#.BLOCK Elements per object
#type#.POOL_SIZE Pool size (aligned to block)
#type#.pool[] Data array
#type#.is_alloc[] Allocation status
#type#.count Live object count
#type#.#field# Property for each field

Memory Layout

Pool: [obj0.f0][obj0.f1][obj0.f2][obj1.f0][obj1.f1][obj1.f2]...
       |<----- BLOCK ------>|     |<----- BLOCK ------>|

Object data starts at ref * BLOCK. Pool size is aligned: #pool# - (#pool# mod BLOCK).


Pool Functions

Generate with type.Functions(MyType).

Allocation

Function Description
Type.new() -> ref Allocate, initialize, call constructor. Returns -1 if pool full
Type.new.ICB() -> ref ICB variant for on init context
Type.copy(src) -> ref Allocate, copy fields, call copy constructor
Type.copy.ICB(src) -> ref ICB variant
declare my_ref := MyType.new()
if my_ref # -1
    MyType[my_ref].value := 42
end if

declare clone := Voice.copy(original)

Deallocation

Function Description
Type.delete(ref) Call destructor, release to pool
Type.delete.ICB(ref) ICB variant
Type.clear_pool() Delete all allocated objects

Utilities

Function Description
Type.init(ref) Reset fields to defaults
Type.clear_mem(ref) Set all fields to TYPE.MEM_CLEAR
Type.empty_fields(ref) Set all fields to -1
Type.copy_a_to_b(a, b) Copy all fields between objects
Type.print(ref) Debug output
Type.repr(ref) -> str String representation

Property Access

Type[ref].field

declare note := Voice[vo_ref].note     // getter
Voice[vo_ref].vel := 127               // setter

Compiles to: Voice.pool[vo_ref * Voice.BLOCK + Voice.field.note]

Type[ref].access[index]

Dynamic field access for iteration:

for i := 0 to MyType.field.SIZE - 1
    MyType[ref].access[i] := 0
end for

Field Enumeration

define Voice.MEMBERS := note, vel, pan, volume
// Generates: Voice.field.note=0, Voice.field.vel=1, Voice.field.pan=2, Voice.field.volume=3
// Voice.field.SIZE = 4

Field indices for reset lists and conditional logic:

declare Voice.RESET_ON_NEW[] := (Voice.field.input, Voice.field.runtime, Voice.field.auto_release)

Reference Validation

check_ref(#type#, #ref#)

define check_ref(#type#, #ref#) := (#ref# > -1 and #type#.is_alloc[#ref#] > 0)

if check_ref(Voice, vo_ref)
    Voice[vo_ref].note := 60
end if

Always validate after wait statements (objects may be deleted during waits):

ivls.wait_ms(100)
if check_ref(Voice, self)
    Voice[self].volume := new_volume
end if

Constructor/Destructor Patterns

macro Voice.Constructor(#ref#)
    declare ref := #ref#
    __RUN_CB__(VoiceConstructor)
end macro

macro Voice.Destructor(#ref#)
    declare ref := #ref#
    __RUN_CB__(VoiceDestructor)
end macro

macro Entry.Destructor(ref)
    declare prev := Entry[ref].prev
    declare next := Entry[ref].next
    if check_ref(Entry, prev)
        Entry[prev].next := next
    end if
    if check_ref(Entry, next)
        Entry[next].prev := prev
    end if
end macro

Local Variable Helpers

Macro Description
type.Locals(T, ref) Declare prefixed locals from object
type.FillLocals(T, ref) Fill prefixed locals from object
type.FillObj(T, ref) Write prefixed locals back to object
type.RawLocals(T, ref) Declare unprefixed locals from object

Complete Example

macro Entry.def()
    define Entry.MEMBERS := prev, next, data
    declare Entry.INIT[] := (-1, -1, 0)
end macro

macro Entry.Destructor(ref)
    declare prev := Entry[ref].prev
    declare next := Entry[ref].next
    if check_ref(Entry, prev)
        Entry[prev].next := next
    end if
    if check_ref(Entry, next)
        Entry[next].prev := prev
    end if
end macro

node Stl.Lists:
    cb Init:
        type.Create(Entry)
    cb Functions:
        type.Functions(Entry)

        function list_append(head_ref, data) -> new_entry
            new_entry := Entry.new()
            Entry[new_entry].data := data
            if check_ref(Entry, head_ref)
                Entry[new_entry].prev := head_ref
                Entry[head_ref].next := new_entry
            end if
        end function
end node

See Also

  • Clusters - Async dispatch using the type system
  • Macros - Type generation macros
  • Defines - Member list definitions