-
Notifications
You must be signed in to change notification settings - Fork 20
struct_definition
Structs are defined by the struct.struct
method. The first, optional parameter is a metadata table. The second argument is a table describing the fields of the struct.
The table is a key-value mapping of final struct fields and their respective descriptions:
struct.struct({
field_a = description_a,
field_b = description_b,
field_c = description_c,
--[[ ... ]]
})
A description is always a table that can have one of three formats:
- Type fields:
field_c = {type = some_ftype}
- Getter fields:
field_a = {get = some_function}
- Data fields:
field_b = {data = anything}
These kind of struct fields define a simple C-based field on the struct:
struct.struct({
int_field = {type = struct.int32},
float_field = {type = struct.float},
string_field = {type = struct.string(0x10)},
})
This would result in the following C struct:
struct {
int32_t int_field;
float float_field;
char string_field[16];
};
These fields are, by far, the most common. As such they have a shortcut, by omitting the type
key. The struct from before can be rewritten as follows:
struct.struct({
int_field = {struct.int32},
float_field = {struct.float},
string_field = {struct.string(0x10)},
})
Optionally a position can be provided where to place the field (if memory layout matters, such as for packets or emulating in-game memory structs). The position can either be specified with a position
key or by omitting the key and placing it in the first position of the table, before the type:
struct.struct({
int_field = {0x00, struct.int32},
float_field = {type = struct.float, position = 0x10},
string_field = {struct.string(0x10), position = 0x2C},
})
The table created here is almost identical to the first two, except that the position of the field is not contiguous. In cases like this, with gaps in the struct field positions the intermediate space is filled up with dummy fields:
struct {
int32_t int_field;
char __1[12];
float float_field;
char __2[20];
char string_field[16];
};
This kind of field description results in a getter metamethod on the struct which, when accessed, calls the provided function with the cdata
itself as an argument. It is used to create logical fields that can be calculated based on other, existing fields and possibly other upvalues:
struct.struct({
first = {struct.int32},
second = {struct.int32},
max = {get = function(data)
return math.max(data.first, data.second)
end},
})
This results in the same C struct as if it was defined without the getter:
struct {
int32_t first;
int32_t second;
};
However, this struct is assigned a metamethod which, when the dummy field max
is accessed, invokes the function with the cdata
object:
local object = struct.new(struct.struct({
first = {struct.int32},
second = {struct.int32},
max = {get = function(data)
return math.max(data.first, data.second)
end},
}))
object.first = 3
object.second = 7
print(object.max) -- 7
In our libraries this is commonly used to define resource-lookups in a struct:
local object = struct.new(struct.struct({
zone_id = {struct.int16},
zone = {get = function(data)
return client_data.zones[data.zone_id]
end}
}))
object.zone_id = 101
print(object.zone.name) -- East Ronfaure
This kind of field description simply assigns the provided value to the field and returns it when accessed. As with the getter, this is done via metamethods and does not show up in the C definition. This has the advantage of being able to hold any type, like regular Lua tables. In our libraries it is most often used for events:
local object = struct.new(struct.struct({
zone_change = {data = event.new()},
}))
packet.incoming[0x00A]:register(function()
object.zone_change:trigger()
end)
Fields can take any other metadata. This can later be used when dealing with the ftype
:
local ftype = struct.struct({
int_field = {struct.int32, order = 'first'},
float_field = {struct.float, order = 'third', optional = true},
bool_field = {struct.bool, order = 'second', true_value = 3, false_value = -1},
})
local object = struct.new(ftype)
local assign_bool = function(object, ftype, value)
object.bool_field = value and ftype.fields.bool_field.true_value or ftype.fields.bool_field.false_value
end
assign_bool(object, ftype, false) -- assigns -1 to the bool_field
In this case the keys order
, optional
, true_value
and false_value
are not part of our API and do nothing by default. But they do persist and can be used outside of our API, as displayed above.
The following are all keys used by the struct
library and should not be used by developers:
-
type
,position
,get
,data
as defined above -
offset
used for bit-packed values that don't start at an 8-bit boundary -
label
used to represent the outside visible key to use as the index on the resulting struct -
cname
used as the internal key to thecdata
, which can be different fromlabel
in certain cases (like reserved keyword types, or converter fields) -
internal
used to designate fields internal to thestruct
library and not for outside use (used for VLA delimiters)
- Background and Architecture
- Windower Data Locations
- Code Standards and Guidelines
- Addon Development
- Windower Commands
- Packet Tutorial
- burdometer
- config
- delay_me_not
- distance
- dress_up
- enternity
- fps
- ime
- logger
- party_time
- paste
- pouches
- send
- shortcuts
- speedometer
- target_info
- terminate
- timestamp
- window_title
- Game
- Windower
- General