-
Notifications
You must be signed in to change notification settings - Fork 18
Lists
Lists in PawnPlus use C++'s std::vector
type to represent a linear mutable container with variable size. Lists are heterogenous, meaning they can store values of any kind (internally stored using by-value variants). Their size can be increased by inserting new elements, or decreased by removing existing elements. Lists are not garbage collected and must be deleted explicitly with list_delete
or list_delete_deep
when no longer needed.
New values can be inserted into a list using the list_add
family of functions, one for each type of a variant:
new List:l = list_new();
list_add(l, true);
list_add_arr(l, Float:{1.0, 2.0});
list_add_str(l, "test");
list_add_var(l, VAR_NULL);
Since the internal element type of a list is variant, a value of any kind may be inserted. The value is copied into the list.
Obtaining the values is akin to variants:
new bool:b, Float:f[2], s[5], c;
assert list_get_safe(l, 0, b);
assert list_get_arr_safe(l, 1, f);
assert list_get_arr_safe(l, 2, s);
assert !list_get_safe(l, 3, c); //null has no value
Unlike variants, you can modify any element in a list and overwrite it with a new value, using the list_set
family of functions. It is not possible to add a new element this way; attempts to set elements past the end of the list will fail.
Traversing over values stored in a list is possible in two ways. The first one uses a numerical index into the list, bounded by its size:
new List:l = list_new();
list_add(l, 3);
list_add(l, 4);
list_add(l, 5);
list_add(l, 6);
for(new i = 0; i < list_size(l); i++)
{
printf("%d", list_get(l, i));
}
This way is fine, since accessing a list element by its index takes constant time. However, there is also a C++-like way, using iterator objects and a special piece of syntax:
for_list(i : l)
//for(new Iter:i = list_iter(l); iter_inside(i); iter_move_next(i))
{
printf("%d", iter_get(i));
}
Here, a new iterator is created via list_iter
, initially pointing to the first element in the list. An iterator can point to any element in the list, or to none, which can be checked using iter_inside
.
Lists are not garbage collected, and will reside forever in memory if not deleted. All directly stored variants (i.e. cells, arrays, or nulls) inside will be deleted with the list. However, references to other PawnPlus objects (stored in these cells or arrays) will not be deleted, since they have their own lifetime:
new List:l = list_new();
new List:l2 = list_new();
list_add(l, l2); //the second list is stored by reference
list_delete(l);
assert list_valid(l2); //l2 still exists and must be deleted
For this reason, there exists a function to delete a list recursively, called list_delete_deep
:
new List:l = list_new();
new List:l2 = list_new();
list_add(l, l2);
list_delete_deep(l);
assert !list_valid(l2);
list_delete_deep
assumes that the list owns all objects stored inside, and releases them.
In general, any value may be stored inside a list. However, it is possible to limit the usage of lists at compilation time and restrict it to a certain tag.
new List<Float>:l = list_new<Float>();
list_add<Float>(l, 1.0);
list_add<Float>(l, 1.1);
list_add<Float>(l, true); //tag mismatch
assert list_tagof(List:l, 2) == (tagof(Float:));
new Float:f = list_get<Float>(l, 2);
Even though an attempt to insert a bool
value is made, the value will still be inserted tagged as Float
. All sensible functions will be usable, and any attempt to call a generic function on a concrete list will fail. However, since the restriction is not present at run-time, retagging a list to its base tag will allow inserting any values to it:
new List<Float>:l = list_new<Float>();
list_add(List:l, false);
assert list_tagof(List:l, 0) != (tagof(Float:));
Notice the need to retag the list even when calling list_tagof
. Since the tag of the value is implied by the explicitly specified tag, the function is not generic.
Generic lists allow storing both single values and arrays with the matching tag.