# Ox Docs ## Project Structure All components have a platform indicator next to them: (OB) - OS, Bare Metal (-B) - Bare Metal (O-) - OS * Ox - Library of things useful for portable bare metal and userland code. Not really that external... * clargs - Command Line Args processing (OB) * claw - Reads and writes Metal or Organic Claw with header to indicate which * event - Qt-like signal system (OB) * fs - file system (OB) * logconn - connects logging to Bullock (O-) * mc - Metal Claw serialization, builds on model (OB) * oc - Organic Claw serialization (wrapper around JsonCpp), builds on model (O-) * model - Data structure modelling (OB) * preloader - library for handling preloading of data (OB) * std - Standard-ish Library with a lot missing and some things added (OB) ## Systems ### Error Handling Ox provides ```ox::Error``` to report errors. ```ox::Error``` is a struct that has overloaded operators to behave like an integer error code, plus some extra fields to enhance debuggability. If instantiated through the ```OxError(x)``` macro, it will also include the file and line of the error. The ```OxError(x)``` macro should only be used for the initial instantiation of an ```ox::Error```. In addition to ```ox::Error``` there is also the template ```ox::Result```. ```ox::Result``` simply wraps the type T value in a struct that also includes error information, which allows the returning of a value and an error without resorting to output parameters. If a function returns an ```ox::Error``` or ```ox::Result``` it should be declared as ```noexcept``` and all exceptions should be translated to an ```ox::Error```. ```ox::Result``` can be used as follows: ```cpp ox::Result foo(int i) noexcept { if (i < 10) { return i + 1; // implicitly calls ox::Result::Result(T) } return OxError(1); // implicitly calls ox::Result::Result(ox::Error) } int caller1() { auto v = foo(argc); if (v.error) { return 1; } std::cout << v.value << '\n'; return 0; } int caller2() { // it is also possible to capture the value and error in their own variables auto [val, err] = foo(argc); if (err) { return 1; } std::cout << val << '\n'; return 0; } ox::Error caller3(int &i) { return foo(i).moveTo(i); } ox::Error caller4(int &i) { return foo(i).copyTo(i); } int caller5(int i) { return foo(i).unwrap(); // unwrap will kill the program if there is an error } int caller6(int i) { return foo(i).unwrapThrow(); // unwrap will throw if there is an error } int caller7(int i) { return foo(i).or_value(0); // will return 0 if foo returned an error } ox::Result caller8(int i) { return foo(i).to(); // will convert the result of foo to uint64_t } ``` Lastly, there are a few macros available to help in passing ```ox::Error```s back up the call stack, ```OX_RETURN_ERROR```, ```OX_THROW_ERROR```, and ```OX_REQUIRE```. ```OX_RETURN_ERROR``` is by far the more helpful of the two. ```OX_RETURN_ERROR``` will return an ```ox::Error``` if it is not 0 and ```OX_THROW_ERROR``` will throw an ```ox::Error``` if it is not 0. Since ```ox::Error``` is always nodiscard, you must do something with them. In rare cases, you may not have anything you can do with them or you may know the code will never fail in that particular instance. This should be used sparingly. ```cpp void studioCode() { auto [val, err] = foo(1); OX_THROW_ERROR(err); doStuff(val); } ox::Error engineCode() noexcept { auto [val, err] = foo(1); OX_RETURN_ERROR(err); doStuff(val); return {}; } void anyCode() { auto [val, err] = foo(1); std::ignore = err; doStuff(val); } ``` Both macros will also take the ```ox::Result``` directly: ```cpp void studioCode() { auto valerr = foo(1); OX_THROW_ERROR(valerr); doStuff(valerr.value); } ox::Error engineCode() noexcept { auto valerr = foo(1); OX_RETURN_ERROR(valerr); doStuff(valerr.value); return {}; } ``` Ox also has the ```OX_REQUIRE``` macro, which will initialize a value if there is no error, and return if there is. It aims to somewhat emulate the ```?``` operator in Rust and Swift. Rust ```?``` operator: ```rust fn f() -> Result { // do stuff } fn f2() -> Result { let i = f()?; Ok(i + 4) } ``` ```OX_REQUIRE```: ```cpp ox::Result f() noexcept { // do stuff } ox::Result f2() noexcept { OX_REQUIRE(i, f()); // const auto [out, OX_CONCAT(oxRequire_err_, __LINE__)] = x; OX_RETURN_ERROR(OX_CONCAT(oxRequire_err_, __LINE__)) return i + 4; } ``` ```OX_REQUIRE``` is not quite as versatile, but it should still cleanup a lot of otherwise less ideal code. ```OX_REQUIRE``` by default creates a const, but there is also an ```OX_REQUIRE_M``` (OX_REQUIRE Mutable) variant for creating a non-const value. * ```OX_REQUIRE_M``` - OX_REQUIRE Mutable ### Ox String Types Ox has six different major string types. These types are divided into two categories: store types and view types. String stores maintain a copy of the string data, whereas view types only maintain a reference to the data. Views should be used where you otherwise might use a const reference to a string store type. #### String Store Types ##### String ```ox::String```, or really ```ox::BasicString```, is Ox's version of ```std::string```. Like ```std::string```, ```String``` allocates to store the string data. Also like ```std::string```, ```String``` allows for small string optimization for strings under 8 bytes. Unlike ```std::string```, the template that ```String``` is based on, ```BasicString```, takes a parameter that allows adjusting to different size small string buffers. ```ox::String``` is an alias to ```ox::BasicString<8>```. ```cpp // s can hold up to 100 bytes, plus one for a null terminator before allocating ox::BasicString<100> s; ``` Also ulike ```std::string```, ```String``` has an explicit C-string conversion constructor. This prevents accidental instantiations of ```String```. Consider the following: ```cpp void fStd(std::string const&); void fOx(ox::String const&); int main() { // implicit and silent instantiation of std::string, which includes an // allocation fStd("123456789"); // Will fail to compile: fOx("123456789"); // But explicit String instantiation will work: fOx(ox::String{"123456789"}); } ``` ##### IString ```IString```, or "inline string", is like ```BasicString```, but it will cut off strings that exceed that limit. ```cpp ox::IString<5> s; // s can hold up to 5 characters, plus a null terminator s = "12345"; // valid s = "123456"; // will compile and run, but will get cut off at '5' ``` ##### StringParam ```StringParam``` is a weird type. Because ```String::String(const char*)``` is explicit, it becomes a pain for functions to take ```String```s. ```cpp struct Type { ox::String m_s; explicit Type(ox::String p): m_s(std::move(p)) { } }; void f() { ox::String s{"asdf"}; Type t1{"asdf"}; // invalid - will not compile Type t2{s}; // invalid - will not compile Type t3{std::move(s)}; // valid Type t4{ox::String{"asdf"}}; // valid } ``` ```StringParam``` has implicit conversion constructors, and will appropriately move from r-value ```String```s. It will create a ```String``` if not passed ownership of an existing ```String```. ```StringParam``` can access the string as a view through the ```view()``` function, and the ```String``` inside can be accessed by moving from the ```StringParams```. ```cpp struct Type { ox::String m_s; explicit Type(ox::StringParam p): m_s(std::move(p)) { } }; void f() { ox::String s{"asdf"}; Type t1{"asdf"}; // valid Type t2{s}; // valid Type t3{std::move(s)}; // valid } ``` #### String View Types ##### StringView ```ox::StringView``` is Ox's version of ```std::string_view```. ```StringView``` contains a pointer to a string, along with its size. This should be the normal type taken when a function needs a string that will exist until it returns. ##### CStringView ```CStringView``` is like ```StringView```, but it comes with the promise that the string ends with a null terminator. Accordingly, it has a ```c_str()``` function in addition to the ```data()``` function that ```StringView``` has. ```CStringView``` should be used when wrapping a C API that only takes C strings. ##### StringLiteral ```StringLiteral``` is a string view type, but it kind of straddles the line between view and store types. Creating a ```StringLiteral``` is a promise that you are passing a string literal into the constructor. This means you can treat it like a store, that can be safely used as a copy of the data. Functions that take ```StringLiteral```s are allowed to assume that the data will have no lifetime concerns and hold onto it without any need to make a copy. It has a consteval constructor to enforce the promise that it is a compile time string. ```cpp void f(ox::StringLiteral const&); int main() { f("123456789"); // valid f(ox::String{"123456789"}.c_str()); // invalid - will not compile } ``` #### Other Variants There are a few convenience aliases as well. * StringCR = String const& * StringViewCR = StringView const& * CStringViewCR = CStringView const& String views do not generally need const references, but it does make debugging easier, as we can skip the constructor call if a string view already exists. These kind of aliases probably should not exist for most types, but strings are fundamental and ease of use is desirable. ### Logging and Output Ox provides for logging and debug prints via the ```oxTrace```, ```oxDebug```, and ```oxError``` macros. Each of these also provides a format variation. Ox also provide ```oxOut``` and ```oxErr``` for printing to stdout and stderr. These are intended for permanent messages and always go to stdout and stderr. Tracing functions do not go to stdout unless the OXTRACE environment variable is set. They also print with the channel that they are on, along with file and line. Debug statements go to stdout and go to the logger on the "debug" channel. Where trace statements are intended to be written with thoughtfulness, debug statements are intended to be quick and temporary insertions. Debug statements trigger compilation failures if OX_NODEBUG is enabled when CMake is run, as it is on Jenkins builds, so ```oxDebug``` statements should never be checked in. This makes ```oxDebug``` preferable to other forms of logging, as temporary prints should never be checked in. ```oxError``` always prints. It includes file and line, and is prefixed with a red "ERROR:". It should generally be used conservatively. It should be used only when there is an error that is not technically fatal, but the user almost certainly wants to know about it. ```oxTrace``` and ```oxTracef```: ```cpp void f(int x, int y) { // x = 9, y = 4 oxTrace("nostalgia.core.sdl.gfx") << "f:" << x << y; // Output: "f: 9 4" oxTracef("nostalgia.core.sdl.gfx", "f: {}, {}", x, y); // Output: "f: 9, 4" } ``` ```oxDebug``` and ```oxDebugf```: ```cpp void f(int x, int y) { // x = 9, y = 4 oxDebug() << "f:" << x << y; // Output: "f: 9 4" oxDebugf("f: {}, {}", x, y); // Output: "f: 9, 4" } ``` ```oxError``` and ```oxErrorf```: ```cpp void f(int x, int y) { // x = 9, y = 4 oxError() << "f:" << x << y; // Output: "ERROR: (:): f: 9 4" oxErrorf("f: {}, {}", x, y); // Output: "ERROR: (:): f: 9, 4" } ``` ### Model System Ox has a model system that provides a sort of manual reflection mechanism. Models require a model function for the type that you want to model. It is also good to provide a type name and type version number, though that is not required. The model function takes an instance of the type it is modelling and a template parameter type. The template parameter type must implement the API used in the models, but it can do anything with the data provided to it. Here is an example from the Nostalgia/Core package: ```cpp struct NostalgiaPalette { static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.NostalgiaPalette"; static constexpr auto TypeVersion = 1; ox::Vector colors; }; struct NostalgiaGraphic { static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.NostalgiaGraphic"; static constexpr auto TypeVersion = 1; int8_t bpp = 0; // rows and columns are really only used by TileSheetEditor int rows = 1; int columns = 1; ox::FileAddress defaultPalette; NostalgiaPalette pal; ox::Vector pixels; }; template constexpr ox::Error model(T *h, ox::CommonPtrWith auto *pal) noexcept { h->template setTypeInfo(); // it is also possible to provide the type name and type version as function arguments //h->setTypeInfo("net.drinkingtea.nostalgia.core.NostalgiaPalette", 1); OX_RETURN_ERROR(h->field("colors", &pal->colors)); return {}; } template constexpr ox::Error model(T *h, ox::CommonPtrWith auto *ng) noexcept { h->template setTypeInfo(); OX_RETURN_ERROR(h->field("bpp", &ng->bpp)); OX_RETURN_ERROR(h->field("rows", &ng->rows)); OX_RETURN_ERROR(h->field("columns", &ng->columns)); OX_RETURN_ERROR(h->field("defaultPalette", &ng->defaultPalette)); OX_RETURN_ERROR(h->field("pal", &ng->pal)); OX_RETURN_ERROR(h->field("pixels", &ng->pixels)); return {}; } ``` The model system also provides for unions: ```cpp #include class FileAddress { template friend constexpr Error model(T*, ox::CommonPtrWith auto*) noexcept; public: static constexpr auto TypeName = "net.drinkingtea.ox.FileAddress"; union Data { static constexpr auto TypeName = "net.drinkingtea.ox.FileAddress.Data"; char *path; const char *constPath; uint64_t inode; }; protected: FileAddressType m_type = FileAddressType::None; Data m_data; }; template constexpr Error model(T *h, ox::CommonPtrWith auto *obj) noexcept { h->template setTypeInfo(); OX_RETURN_ERROR(h->fieldCString("path", &obj->path)); OX_RETURN_ERROR(h->fieldCString("constPath", &obj->path)); OX_RETURN_ERROR(h->field("inode", &obj->inode)); return {}; } template constexpr Error model(T *io, ox::CommonPtrWith auto *fa) noexcept { io->template setTypeInfo(); // cannot read from object in Reflect operation if constexpr(ox_strcmp(T::opType(), OpType::Reflect) == 0) { int8_t type = 0; OX_RETURN_ERROR(io->field("type", &type)); OX_RETURN_ERROR(io->field("data", UnionView(&fa->m_data, 0))); } else { auto type = static_cast(fa->m_type); OX_RETURN_ERROR(io->field("type", &type)); fa->m_type = static_cast(type); OX_RETURN_ERROR(io->field("data", UnionView(&fa->m_data, static_cast(fa->m_type)))); } return {}; } ``` There are also macros in `````` for simplifying the declaration of models: ```cpp OX_MODEL_BEGIN(NostalgiaGraphic) OX_MODEL_FIELD(bpp) OX_MODEL_FIELD(rows) OX_MODEL_FIELD(columns) OX_MODEL_FIELD(defaultPalette) OX_MODEL_FIELD(pal) OX_MODEL_FIELD(pixels) oxModelEnd() ``` ### Serialization Using the model system, Ox provides for serialization. Ox has MetalClaw and OrganicClaw as its serialization format options. MetalClaw is a custom binary format designed for minimal size. OrganicClaw is a wrapper around JsonCpp, chosen because it technically implements a superset of JSON. OrganicClaw requires support for 64 bit integers, whereas normal JSON technically does not. These formats do not currently support floats. There is also a wrapper format called Claw that provides a header at the beginning of the file and can dynamically switch between the two depending on what the header says is present. The Claw header also includes information about the type and type version of the data. Claw header: ```M1;net.drinkingtea.nostalgia.core.NostalgiaPalette;1;``` That reads: * Format is Metal Claw, version 1 * Type ID is net.drinkingtea.nostalgia.core.NostalgiaPalette * Type version is 1 #### Metal Claw Example ##### Read ```cpp #include ox::Result loadPalette1(ox::BufferView const&buff) noexcept { return ox::readMC(buff); } ox::Result loadPalette2(ox::BufferView const&buff) noexcept { NostalgiaPalette pal; OX_RETURN_ERROR(ox::readMC(buff, pal)); return pal; } ``` ##### Write ```cpp #include ox::Result writeSpritePalette1(NostalgiaPalette const&pal) noexcept { ox::Buffer buffer(ox::units::MB); std::size_t sz = 0; OX_RETURN_ERROR(ox::writeMC(buffer.data(), buffer.size(), pal, &sz)); buffer.resize(sz); return buffer; } ox::Result writeSpritePalette2(NostalgiaPalette const&pal) noexcept { return ox::writeMC(pal); } ``` #### Organic Claw Example ##### Read ```cpp #include ox::Result loadPalette1(ox::BufferView const&buff) noexcept { return ox::readOC(buff); } ox::Result loadPalette2(ox::BufferView const&buff) noexcept { NostalgiaPalette pal; OX_RETURN_ERROR(ox::readOC(buff, &pal)); return pal; } ``` ##### Write ```cpp #include ox::Result writeSpritePalette1(NostalgiaPalette const&pal) noexcept { ox::Buffer buffer(ox::units::MB); OX_RETURN_ERROR(ox::writeOC(buffer.data(), buffer.size(), pal)); return buffer; } ox::Result writeSpritePalette2(NostalgiaPalette const&pal) noexcept { return ox::writeOC(pal); } ``` #### Claw Example ##### Read ```cpp #include ox::Result loadPalette1(ox::BufferView const&buff) noexcept { return ox::readClaw(buff); } ox::Result loadPalette2(ox::BufferView const&buff) noexcept { NostalgiaPalette pal; OX_RETURN_ERROR(ox::readClaw(buff, pal)); return pal; } ``` ##### Write ```cpp #include ox::Result writeSpritePalette(NostalgiaPalette const&pal) noexcept { return ox::writeClaw(pal); } ```