Packets
While a struct
may reorganize it layout on memory in unexpected ways, packets are made to be a exact
1:1 map of it implementation to memory.
Packets are designed to not just have total precision on it size in memory as too allow the user to have full controll of the layout of all the bits stored on it.
Implementation and Usage
Packets can be implemented as structs, using the @packed
attribute as following:
@packed(8) struct MyDinner {
bool hasFork
bool hasSpoon
bool hasKnife
bool hasPlate
u4 hungerLevel
}
The parameter x in @packed(x)
indicates the total size, in bits, of the object in memory.
The parameter is, however, optional and will be automatically calculated if not provided.
Inside the packet, you can include primitives and other structures.
Packets are used at the same way as structures:
let MyDinner dinner = MyDinner {
hasFork: true,
hasSpoon: true,
hasKnife: true,
hasPlate: false,
hungerLevel: 10
}
A constructor can also be implemented to make the process easy.
and you can use the dot operator (.
) to acess it fields:
import from Std.Console
if (!dinner.hasFork) writeLn("Comrade doesn't have a fork!");
if (!dinner.hasSpoon) writeLn("Comrade doesn't have a spoon!");
if (!dinner.hasKnife) writeLn("Comrade doesn't have a knife!");
if (!dinner.hasPlate) writeLn("Comrade doesn't have a plate!");
if (!dinner.hungerLevel > 10) writeLn("They're starving!!!!");
elif (!dinner.hungerLevel > 5) writeLn("They're hungry!");
Comrade doesn't have a plate!
They're hungry!
Manually Modifying Memory Layout
Sometimes, is necessary to declarate exactly how the memory is organized inside a package.
For this, the standard library defines with the @packed
attribute, the attributes @off
,
@lay
and @pad
(abreviations for offset, layout and padding, respectivelly).
They're used to define the positions and sizes of the next fields inside the structure. \
@off(x)
defines the position of the field based on the origin ofx
bits;@lay(r)
defines the ranger
of the value, in bits, based on the origin;@pad(r)
defines a relative padding left and right based on the ranger
.
As instance, let's suppose we have the following structure layout for a page of a table:
bytes: | 0..4 | 4 | 4..10 | 10..15 | 15..32 |
---|---|---|---|---|---|
data: | tag (enum) | active (bool) | reserved (void) | name (5 * u8) | index (u16) |
As the data follows a structure with specific defined fields, plus there are some values that are not 8-bit alligned, packets should be used to represent this table. A representation of this table should be:
### The range, in bits, are:
- tag: 0 .. 64
- active: 64
- reserved: 64 .. 80
- name: 80 .. 120
- index: 120 .. 256
###
@public @packed struct TablePage {
@lay(..8) Tag tag
bool active # alignment of 1-bits
@off(80)
StringBuffer(5) name # alignment of 8-bits * 5
u16 index # alignment of 16-bits
}
It is also possible to use bytes instead of bits with the range expression's step value.
Bothe @lay
and @pad
attributes will use the step value as a multiplier.
e.g.:
# No need to count the bits, as we are using bytes!
@public @packed struct TablePage {
@lay(..4) Tag tag
bool active # alignment of 1-bits
@off(10)
StringBuffer(5) name # alignment of 8-bits * 5
u16 index # alignment of 16-bits
}