NC* is written in the ODIN programming language. Before we begin, it is expected you have some knowledge of low-level programming languages, or ideally a basic understanding of ODIN. This guide will not be teaching programming.
The reasons to choose ODIN are multifaceted, but the primary reason is its simplicity and approachable design, certainly far easier to pick up than C or C++. There will be references to things like cstring and string, where you will be expected to know how to use them in what circumstances.
As ODIN has no concept of references, we will do our best to explain best practices as the by-value concept may seem strange initially for those used to using references. You very rarely need to dereference a pointer in ODIN, unlike in C/C++ where it's practically everywhere. In fact, as time moves on, it is unlikely NC* will be dereferencing anything.
Other things to note:
string type, or where we make use of the make or new procedure using the standard allocator. The primary and global state structure, UI_State is heap allocated from the UI_Init procedure, but besides this, everything is stack allocated and moved into the state as required. Where we perform allocations into temp_allocator, we would tell you.Content, each Component stores a copy. This may change later, but would not work in Retained-mode. Please keep this in mind when reading these guides, as the Immediate vs Retained will naturally prop up. The goal of NC* is not to repeat the mistakes that almost all Retained-mode API's do, which is being opinionated.Now this is all said and done, let's move on.
There are two different concepts in NC* to understand before we can move onto coding.
ComponentDefinition is used to describe the default construction of a component, which can have one or more Compositions. A Component is something that is created and stored into the global UI stack, which takes a reference to a definition to determine what to draw. Component's will “retain” a copy of a Composition that the Component should draw based on its current state.When creating our own custom components, understanding these two concepts is vital for the construction of these components and how they are drawn.
Below is the expected dependency chain.
Shape → Composition → ComponentDefinition → ComponentAs you can see, there is no data involved. That's because we are storing data globally and accessing it per Component.
When creating definitions, we give an identifier which we use to construct with later. Then, Components are added to the global state from these definitions which are used to create our interface. If they hold content, the Content type is used for holding our content and is assigned to the global state and accessed via the integer identifier given to us from the global stack. We can use this to move data around easily, instead of trying to use procedures to pass around our data all the time.
In the case of Content, we take advantage of the union type in ODIN which we use to hold a certain specific number of types we would accept. This is compile-time types which are easy to type-check and less error-prone than using the any type we could have used. Note that when we use Content, basic types are copied by value as usual, but user-defined types will be pointers to an underlying type. Moreover, when dealing with these pointers, we are expecting the underlying types to be heap allocated, not stack allocated.
The main reason to do this is because Content as a type in the global stack is a value type, not a pointer, so if data is considered “complex” (i.e., not a basic type), then heap allocate the data and make a reference to it for the Content to use later. This prevents loss of data or losing data in scopes where we expected a value copy. You can use the temp_allocator when allocating if a memory arena is preferred over a straight heap allocation, or your own allocator. The reason the Content type is a union is we can expand this to store values of any type which you, the user, can type-check manually later if desired.
Besides the basic structure defined above for building interfaces, everything else is left to the user to define how to structure the Components themselves.
All your member variables, procedures and business logic is done yourself if building components from scratch, and NC* will not care much how that's done, besides one simple caveat:
We try our best to make your life as simple as possible in this regard.
We can move on to the next chapter once you're ready.