479 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			479 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
 | 
						|
# 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<T>```.
 | 
						|
```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<int> foo(int i) noexcept {
 | 
						|
	if (i < 10) {
 | 
						|
		return i + 1; // implicitly calls ox::Result<T>::Result(T)
 | 
						|
	}
 | 
						|
	return OxError(1); // implicitly calls ox::Result<T>::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<uint64_t> caller8(int i) {
 | 
						|
    return foo(i).to<uint64_t>(); // 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<i32, i32> {
 | 
						|
  // do stuff
 | 
						|
}
 | 
						|
 | 
						|
fn f2() -> Result<i32, i32> {
 | 
						|
  let i = f()?;
 | 
						|
  Ok(i + 4)
 | 
						|
}
 | 
						|
```
 | 
						|
 | 
						|
```OX_REQUIRE```:
 | 
						|
```cpp
 | 
						|
ox::Result<int> f() noexcept {
 | 
						|
	// do stuff
 | 
						|
}
 | 
						|
 | 
						|
ox::Result<int> 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
 | 
						|
 | 
						|
### 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: (<file>:<line>): f: 9 4"
 | 
						|
	oxErrorf("f: {}, {}", x, y); // Output: "ERROR: (<file>:<line>): 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<Color16> 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<uint8_t> pixels;
 | 
						|
};
 | 
						|
 | 
						|
template<typename T>
 | 
						|
constexpr ox::Error model(T *h, ox::CommonPtrWith<NostalgiaPalette> auto *pal) noexcept {
 | 
						|
	h->template setTypeInfo<NostalgiaPalette>();
 | 
						|
	// 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<typename T>
 | 
						|
constexpr ox::Error model(T *h, ox::CommonPtrWith<NostalgiaGraphic> auto *ng) noexcept {
 | 
						|
	h->template setTypeInfo<NostalgiaGraphic>();
 | 
						|
	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 <ox/model/types.hpp>
 | 
						|
 | 
						|
class FileAddress {
 | 
						|
 | 
						|
	template<typename T>
 | 
						|
	friend constexpr Error model(T*, ox::CommonPtrWith<FileAddress> 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<typename T>
 | 
						|
constexpr Error model(T *h, ox::CommonPtrWith<FileAddress::Data> auto *obj) noexcept {
 | 
						|
	h->template setTypeInfo<FileAddress::Data>();
 | 
						|
	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<typename T>
 | 
						|
constexpr Error model(T *io, ox::CommonPtrWith<FileAddress> auto *fa) noexcept {
 | 
						|
	io->template setTypeInfo<FileAddress>();
 | 
						|
	// 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<int8_t>(fa->m_type);
 | 
						|
		OX_RETURN_ERROR(io->field("type", &type));
 | 
						|
		fa->m_type = static_cast<FileAddressType>(type);
 | 
						|
		OX_RETURN_ERROR(io->field("data", UnionView(&fa->m_data, static_cast<int>(fa->m_type))));
 | 
						|
	}
 | 
						|
	return {};
 | 
						|
}
 | 
						|
 | 
						|
```
 | 
						|
 | 
						|
There are also macros in ```<ox/model/def.hpp>``` 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/mc/read.hpp>
 | 
						|
 | 
						|
ox::Result<NostalgiaPalette> loadPalette1(ox::BufferView const&buff) noexcept {
 | 
						|
	return ox::readMC<NostalgiaPalette>(buff);
 | 
						|
}
 | 
						|
 | 
						|
ox::Result<NostalgiaPalette> loadPalette2(ox::BufferView const&buff) noexcept {
 | 
						|
	NostalgiaPalette pal;
 | 
						|
	OX_RETURN_ERROR(ox::readMC(buff, pal));
 | 
						|
	return pal;
 | 
						|
}
 | 
						|
```
 | 
						|
 | 
						|
##### Write
 | 
						|
 | 
						|
```cpp
 | 
						|
#include <ox/mc/write.hpp>
 | 
						|
 | 
						|
ox::Result<ox::Buffer> 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<ox::Buffer> writeSpritePalette2(NostalgiaPalette const&pal) noexcept {
 | 
						|
	return ox::writeMC(pal);
 | 
						|
}
 | 
						|
```
 | 
						|
 | 
						|
#### Organic Claw Example
 | 
						|
 | 
						|
##### Read
 | 
						|
 | 
						|
```cpp
 | 
						|
#include <ox/oc/read.hpp>
 | 
						|
 | 
						|
ox::Result<NostalgiaPalette> loadPalette1(ox::BufferView const&buff) noexcept {
 | 
						|
	return ox::readOC<NostalgiaPalette>(buff);
 | 
						|
}
 | 
						|
 | 
						|
ox::Result<NostalgiaPalette> loadPalette2(ox::BufferView const&buff) noexcept {
 | 
						|
	NostalgiaPalette pal;
 | 
						|
	OX_RETURN_ERROR(ox::readOC(buff, &pal));
 | 
						|
	return pal;
 | 
						|
}
 | 
						|
```
 | 
						|
 | 
						|
##### Write
 | 
						|
 | 
						|
```cpp
 | 
						|
#include <ox/oc/write.hpp>
 | 
						|
 | 
						|
ox::Result<ox::Buffer> 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<ox::Buffer> writeSpritePalette2(NostalgiaPalette const&pal) noexcept {
 | 
						|
	return ox::writeOC(pal);
 | 
						|
}
 | 
						|
```
 | 
						|
 | 
						|
#### Claw Example
 | 
						|
 | 
						|
##### Read
 | 
						|
 | 
						|
```cpp
 | 
						|
#include <ox/claw/read.hpp>
 | 
						|
 | 
						|
ox::Result<NostalgiaPalette> loadPalette1(ox::BufferView const&buff) noexcept {
 | 
						|
	return ox::readClaw<NostalgiaPalette>(buff);
 | 
						|
}
 | 
						|
 | 
						|
ox::Result<NostalgiaPalette> loadPalette2(ox::BufferView const&buff) noexcept {
 | 
						|
	NostalgiaPalette pal;
 | 
						|
	OX_RETURN_ERROR(ox::readClaw(buff, pal));
 | 
						|
	return pal;
 | 
						|
}
 | 
						|
```
 | 
						|
 | 
						|
##### Write
 | 
						|
 | 
						|
```cpp
 | 
						|
#include <ox/claw/write.hpp>
 | 
						|
 | 
						|
ox::Result<ox::Buffer> writeSpritePalette(NostalgiaPalette const&pal) noexcept {
 | 
						|
	return ox::writeClaw(pal);
 | 
						|
}
 | 
						|
```
 |