-
-
Notifications
You must be signed in to change notification settings - Fork 23
Onyx Tags
Tags are a rather interesting feature of Onyx that enables the programmer to include metadata about procedures and types directly in the program, such that the program can use it at runtime for any purpose.
A tag is simply a compile-time known value associated with something that is stored in compiled-binary. It can be associated with one of several objects in the program:
- On a procedure
- On a structure
- On a structure's member
There may be more places in the future to place tags.
Simply use the #tag
directive before the object to add the tag to it. Tags can be of any types, but their value must be known at compile-time.
// String literals are compile-time known.
#tag "Some string"
some_procedure :: () { }
Metadata :: struct { author: str; docstring: str; }
// Multiple tags can be placed on the same object.
#tag "Some other string"
#tag Metadata.{"Me", "This is a simple procedure."}
simple_procedure :: () { }
// Integer literals are compile-time known.
#tag 1234
Some_Structure :: struct { }
Parser_Options :: enum {
Skip;
}
Simple_Structure :: struct {
// Enum values are compile-time known.
#tag Parser_Options.Skip
temporary_member: u32;
}
Tags are stored in different places depending on what object was tagged.
For procedures, tags are placed in core.runtime.info.tagged_procedures
. This array contains an entry for every procedure that was tagged, and on every entry is an array of the tags for that procedure. There are two convenience procedures in core.runtime.info
for working with this array.
-
get_tags_for_procedure(procedure)
- Given a procedure, it finds and returns the list of tags on that procedure, or an empty array. -
get_procedures_with_tag(tag_type)
- Given atype_expr
, it finds and returns all of the procedures that have a tag of that type, as well as the value of the tag. If multiple tags with the same type exist for the same procedure, only the first one is returned. This procedure returns as an array of opaque structures that has the following members:-
proc
- The procedure with the tag -
type
- The type of the procedure -
tag
- The value of the tag
-
For structures, tags are placed in the Type_Info_Struct
type info for a given structure. They can be accessed like so:
use core.runtime.info
info := cast(^Type_Info_Struct) get_type_info(Some_Structure);
info.tags
For structure members, tags are placed in the Type_Info_Struct.Member
type. Use core.runtime.info.get_tags_for_member(type, member_name)
procedure to easily access this array.
Just about anything. Since tags are not particular about the type or meaning of anything used. It is up to your program to decide what they should do.
A common use case of them is to enable a form of "dynamic loading" of pieces of the codebase, without needing to modify a central registration block of code. For example, object types in a game could be registered manually in the following way.
//...
registry->add(Player, "Player", "The player entity");
registry->add(Enemy, "Enemy", "A generic enemy");
registry->add(Bullet, "Bullet", "A generic bullet");
//...
This becomes cumbersome when there are many things to register, and leaves the programmer to remember that an entry has to be made here. A better alternative would be to use tags on structures to signify that a structure is a game object type.
//player.onyx
#tag GameObject.{"The player entity"}
Player :: struct { }
//enemy.onyx
#tag GameObject.{"A generic enemy"}
Enemy :: struct { }
//bullet.onyx
#tag GameObject.{"A generic bullet"}
Bullet :: struct { }
In this case, the engine could looks for all structures with a tag of type GameObject
, then register them automatically.