2022-02-26 22:52:35 -06:00
|
|
|
# Nostalgia Developer Handbook
|
2019-11-23 01:13:26 -06:00
|
|
|
|
2019-11-26 23:35:24 -06:00
|
|
|
## About
|
|
|
|
|
2021-07-17 18:16:32 -05:00
|
|
|
The purpose of the Developer Handbook is similar to that of the README.
|
|
|
|
The README should be viewed as a prerequisite to the Developer Handbook.
|
|
|
|
The README should provide information needed to build the project, which might
|
|
|
|
be used by an advanced user or a person trying to build and package the
|
|
|
|
project.
|
|
|
|
The Developer Handbook should focus on information needed by a developer
|
|
|
|
working on the project.
|
2019-11-26 23:35:24 -06:00
|
|
|
|
2019-11-23 01:13:26 -06:00
|
|
|
## Project Structure
|
|
|
|
|
|
|
|
### Overview
|
|
|
|
|
|
|
|
All components have a platform indicator next to them:
|
|
|
|
|
|
|
|
(PG) - PC, GBA
|
|
|
|
(-G) - GBA
|
|
|
|
(P-) - PC
|
|
|
|
|
|
|
|
* Nostalgia
|
2023-11-23 18:22:22 -06:00
|
|
|
* modules
|
|
|
|
* core - graphics system for Nostalgia (PG)
|
|
|
|
* gba - GBA implementation (PG)
|
|
|
|
* opengl - OpenGL implementation (P-)
|
|
|
|
* studio - studio plugin for core (P-)
|
|
|
|
* keel - keel plugin for core (PG)
|
|
|
|
* scene - defines & processes map data (PG)
|
|
|
|
* studio - studio plugin for scene (P-)
|
|
|
|
* keel - keel plugin for scene (PG)
|
2019-11-23 01:13:26 -06:00
|
|
|
* player - plays the games (PG)
|
|
|
|
* studio - makes the games (P-)
|
|
|
|
* tools - command line tools (P-)
|
|
|
|
* pack - packs a studio project directory into an OxFS file (P-)
|
2023-12-23 19:08:18 -06:00
|
|
|
* Olympic
|
|
|
|
* Applib - Library for creating apps as libraries that injects Keel and Studio modules
|
|
|
|
* Keel - asset management system (PG)
|
|
|
|
* Studio - where most of the studio code lives as library (P-)
|
|
|
|
* applib - used for per project studio executables
|
|
|
|
* modlib - used for studio modules to interact with studio
|
|
|
|
* Turbine - platform abstraction and user I/O (PG)
|
|
|
|
* gba - GBA implementation (PG)
|
|
|
|
* glfw - GLFW implementation (P-)
|
2019-11-23 01:13:26 -06:00
|
|
|
* deps - project dependencies
|
|
|
|
* Ox - Library of things useful for portable bare metal and userland code. Not really that external...
|
2022-02-10 02:05:11 -06:00
|
|
|
* clargs - Command Line Args processing (PG)
|
2020-07-29 00:26:20 -05:00
|
|
|
* claw - Reads and writes Metal or Organic Claw with header to indicate which
|
2023-11-23 18:22:22 -06:00
|
|
|
* event - Qt-like signal system
|
2019-11-23 01:13:26 -06:00
|
|
|
* fs - file system (PG)
|
2023-11-23 18:22:22 -06:00
|
|
|
* logconn - connects logging to Bullock (P-)
|
2019-11-23 01:13:26 -06:00
|
|
|
* mc - Metal Claw serialization, builds on model (PG)
|
2020-07-29 00:26:20 -05:00
|
|
|
* oc - Organic Claw serialization (wrapper around JsonCpp), builds on model (P-)
|
2019-11-23 01:13:26 -06:00
|
|
|
* model - Data structure modelling (PG)
|
2023-11-23 18:22:22 -06:00
|
|
|
* preloader - library for handling preloading of data (PG)
|
2019-11-23 01:13:26 -06:00
|
|
|
* std - Standard-ish Library with a lot missing and some things added (PG)
|
2023-12-23 19:08:18 -06:00
|
|
|
* GlUtils - OpenGL helpers (P-)
|
2023-11-23 18:22:22 -06:00
|
|
|
* teagba - GBA assembly startup code (mostly pulled from devkitPro under MPL
|
|
|
|
2.0), and custom GBA hardware interop code (-G)
|
2019-11-23 01:13:26 -06:00
|
|
|
|
2023-12-23 19:08:18 -06:00
|
|
|
## Platform Notes
|
|
|
|
|
|
|
|
### GBA
|
|
|
|
|
2023-12-29 19:02:38 -06:00
|
|
|
The GBA has two major resources for learning about its hardware:
|
|
|
|
|
|
|
|
* [Tonc](https://www.coranac.com/tonc/text/toc.htm) - This is basically a short
|
|
|
|
book on the GBA and low level development.
|
|
|
|
* [GBATEK](https://rust-console.github.io/gbatek-gbaonly/) - This is a more
|
|
|
|
concise resource that mostly tells about memory ranges and registers.
|
|
|
|
|
2023-12-23 19:08:18 -06:00
|
|
|
#### Graphics
|
|
|
|
|
|
|
|
* Background Palette: 256 colors
|
|
|
|
* Sprite Palette: 256 colors
|
|
|
|
|
2019-11-23 01:13:26 -06:00
|
|
|
## Code Base Conventions
|
|
|
|
|
|
|
|
### Formatting
|
|
|
|
|
|
|
|
* Indentation is done with tabs.
|
|
|
|
* Alignment is done with spaces.
|
|
|
|
* Opening brackets go on the same line as the thing they are opening for (if,
|
|
|
|
while, for, try, catch, function, etc.)
|
|
|
|
* No space between function parentheses and arguments.
|
|
|
|
* Spaces between arithmetic/bitwise/logical/assignment operands and operators.
|
|
|
|
* Pointer and reference designators should be bound to the identifier name and
|
2021-03-16 21:16:19 -05:00
|
|
|
not the type, unless there is not identifier name, in which case it should be
|
|
|
|
bound to the type.
|
2019-11-23 01:13:26 -06:00
|
|
|
|
2020-06-29 11:31:11 -05:00
|
|
|
### Write C++, Not C
|
|
|
|
|
|
|
|
On the surface, it seems like C++ changes the way we do things from C for no
|
|
|
|
reason, but there are reasons for many of these duplications of functionality.
|
2020-07-29 00:26:20 -05:00
|
|
|
The C++ language designers aren't stupid. Question them, but don't ignore them.
|
2020-06-29 11:31:11 -05:00
|
|
|
|
|
|
|
#### Casting
|
|
|
|
|
2021-07-17 18:16:32 -05:00
|
|
|
Do not use C-style casts.
|
|
|
|
C++ casts are more readable, and more explicit about the type of cast being
|
|
|
|
used.
|
|
|
|
Do not use ```dynamic_cast``` in code building for the GBA, as RTTI is disabled
|
|
|
|
in GBA builds.
|
2020-06-29 11:31:11 -05:00
|
|
|
|
|
|
|
#### Library Usage
|
|
|
|
|
2021-07-17 18:16:32 -05:00
|
|
|
C++ libraries should generally be preferred to C libraries.
|
|
|
|
C libraries are allowed, but pay extra attention.
|
2020-06-29 11:31:11 -05:00
|
|
|
|
|
|
|
This example from nostalgia::core demonstrates the type of problems that can
|
|
|
|
arise from idiomatically mixed code.
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
uint8_t *loadRom(const char *path) {
|
|
|
|
auto file = fopen(path, "r");
|
|
|
|
if (file) {
|
|
|
|
fseek(file, 0, SEEK_END);
|
|
|
|
const auto size = ftell(file);
|
|
|
|
rewind(file);
|
2022-02-26 22:52:35 -06:00
|
|
|
// new can technically throw, though this project considers out-of-memory
|
|
|
|
// to be unrecoverable
|
2020-06-29 11:31:11 -05:00
|
|
|
auto buff = new uint8_t[size];
|
|
|
|
fread(buff, size, 1, file);
|
|
|
|
fclose(file);
|
|
|
|
return buff;
|
|
|
|
} else {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
In practice, that particular example is not something we really care about
|
|
|
|
here, but it does demonstrate that problems can arise when mixing what might be
|
|
|
|
perceived as cool old-school C-style code with lame seemingly over-complicated
|
|
|
|
C++-style code.
|
|
|
|
|
|
|
|
Here is another more concrete example observed in another project:
|
|
|
|
```cpp
|
|
|
|
int main() {
|
|
|
|
// using malloc does not call the constructor
|
|
|
|
std::vector<int> *list = (std::vector<int>*) malloc(sizeof(std::vector<int>));
|
|
|
|
doStuff(list);
|
2022-02-26 22:52:35 -06:00
|
|
|
// free does not call the destructor, which causes memory leak for array
|
|
|
|
// inside list
|
2020-06-29 11:31:11 -05:00
|
|
|
free(list);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
The code base where this was observed actually got away with this for the most
|
2021-02-26 08:55:40 -06:00
|
|
|
part, as the std::vector implementation used evidently waited until the
|
2020-06-29 11:31:11 -05:00
|
|
|
internal array was needed before initializing and the memory was zeroed out
|
2021-07-17 18:16:32 -05:00
|
|
|
because the allocation occurred early in the program's execution.
|
|
|
|
While the std::vector implementation in question worked with this code and the
|
|
|
|
memory leak is not noticeable because the std::vector was meant to exist for
|
|
|
|
the entire life of the process, other classes likely will not get away with it
|
|
|
|
due to more substantial constructors and more frequent instantiations of the
|
|
|
|
classes in question.
|
2020-06-29 11:31:11 -05:00
|
|
|
|
2021-10-23 14:10:16 -05:00
|
|
|
## Project Systems
|
2019-11-23 01:13:26 -06:00
|
|
|
|
|
|
|
### Error Handling
|
|
|
|
|
2021-10-23 14:10:16 -05:00
|
|
|
Exceptions are clean and nice in userland code running in environments with
|
2022-02-26 22:52:35 -06:00
|
|
|
expansive system resources, but they are a bit of a pain in small bare metal
|
|
|
|
environments.
|
2021-07-17 18:16:32 -05:00
|
|
|
The GBA build has them disabled.
|
2022-02-26 22:52:35 -06:00
|
|
|
Exceptions cause also the compiler to generate a great deal of extra code that
|
2021-07-17 18:16:32 -05:00
|
|
|
inflates the size of the binary.
|
|
|
|
The binary size bloat is often cited as one of the main reasons why many
|
|
|
|
embedded developers prefer C to C++.
|
2019-11-23 01:13:26 -06:00
|
|
|
|
2021-10-23 14:10:16 -05:00
|
|
|
Instead of throwing exceptions, all engine code must return Ox error codes.
|
|
|
|
For the sake of consistency, try to stick to Ox error codes in non-engine code
|
|
|
|
as well.
|
2021-05-03 20:51:56 -05:00
|
|
|
Nostalgia and Ox both use ```ox::Error``` to report errors. ```ox::Error``` is
|
|
|
|
a struct that has overloaded operators to behave like an integer error code,
|
2021-07-17 18:16:32 -05:00
|
|
|
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.
|
2021-05-03 20:51:56 -05:00
|
|
|
The ```OxError(x)``` macro should only be used for the initial instantiation of
|
|
|
|
an ```ox::Error```.
|
2019-11-23 01:13:26 -06:00
|
|
|
|
2021-03-16 21:16:19 -05:00
|
|
|
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
|
2019-11-23 01:13:26 -06:00
|
|
|
error information, which allows the returning of a value and an error without
|
|
|
|
resorting to output parameters.
|
|
|
|
|
2021-07-06 20:36:22 -05:00
|
|
|
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```.
|
|
|
|
|
2021-03-16 21:16:19 -05:00
|
|
|
```ox::Result``` can be used as follows:
|
2019-11-23 01:13:26 -06:00
|
|
|
|
|
|
|
```cpp
|
2021-10-23 14:10:16 -05:00
|
|
|
ox::Result<int> foo(int i) noexcept {
|
2019-11-23 01:13:26 -06:00
|
|
|
if (i < 10) {
|
2021-03-16 21:16:19 -05:00
|
|
|
return i + 1; // implicitly calls ox::Result<T>::Result(T)
|
2019-11-23 01:13:26 -06:00
|
|
|
}
|
2021-03-16 21:16:19 -05:00
|
|
|
return OxError(1); // implicitly calls ox::Result<T>::Result(ox::Error)
|
2019-11-23 01:13:26 -06:00
|
|
|
}
|
2019-11-26 23:35:24 -06:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
2019-11-23 01:13:26 -06:00
|
|
|
```
|
|
|
|
|
2021-03-18 01:33:29 -05:00
|
|
|
Lastly, there are a few macros available to help in passing ```ox::Error```s
|
2024-04-22 23:40:48 -05:00
|
|
|
back up the call stack, ```oxReturnError```, ```oxThrowError```, and
|
|
|
|
```oxRequire```.
|
2019-11-23 01:13:26 -06:00
|
|
|
|
2021-07-17 18:16:32 -05:00
|
|
|
```oxReturnError``` is by far the more helpful of the two.
|
|
|
|
```oxReturnError``` will return an ```ox::Error``` if it is not 0 and
|
|
|
|
```oxThrowError``` will throw an ```ox::Error``` if it is not 0.
|
|
|
|
Because exceptions are disabled for GBA builds and thus cannot be used in the
|
|
|
|
engine, ```oxThrowError``` is only really useful at the boundary between
|
|
|
|
engine libraries and Nostalgia Studio.
|
2019-11-23 01:13:26 -06:00
|
|
|
|
2021-10-23 14:20:15 -05:00
|
|
|
Since ```ox::Error``` is always nodiscard, you must do something with them.
|
2024-04-22 23:40:48 -05:00
|
|
|
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.
|
2021-03-16 21:16:19 -05:00
|
|
|
|
|
|
|
|
2019-11-23 01:13:26 -06:00
|
|
|
```cpp
|
|
|
|
void studioCode() {
|
|
|
|
auto [val, err] = foo(1);
|
|
|
|
oxThrowError(err);
|
2019-11-26 23:35:24 -06:00
|
|
|
doStuff(val);
|
2019-11-23 01:13:26 -06:00
|
|
|
}
|
|
|
|
|
2021-10-23 14:10:16 -05:00
|
|
|
ox::Error engineCode() noexcept {
|
2019-11-23 01:13:26 -06:00
|
|
|
auto [val, err] = foo(1);
|
|
|
|
oxReturnError(err);
|
2019-11-26 23:35:24 -06:00
|
|
|
doStuff(val);
|
2019-11-23 01:13:26 -06:00
|
|
|
return OxError(0);
|
|
|
|
}
|
2021-03-16 21:16:19 -05:00
|
|
|
|
|
|
|
void anyCode() {
|
|
|
|
auto [val, err] = foo(1);
|
2024-04-22 23:40:48 -05:00
|
|
|
std::ignore = err;
|
2021-03-16 21:16:19 -05:00
|
|
|
doStuff(val);
|
|
|
|
}
|
2019-11-23 01:13:26 -06:00
|
|
|
```
|
|
|
|
|
2021-03-16 21:16:19 -05:00
|
|
|
Both macros will also take the ```ox::Result``` directly:
|
2019-11-23 01:13:26 -06:00
|
|
|
|
|
|
|
```cpp
|
|
|
|
void studioCode() {
|
|
|
|
auto valerr = foo(1);
|
|
|
|
oxThrowError(valerr);
|
2019-11-26 23:35:24 -06:00
|
|
|
doStuff(valerr.value);
|
2019-11-23 01:13:26 -06:00
|
|
|
}
|
|
|
|
|
2021-10-23 14:10:16 -05:00
|
|
|
ox::Error engineCode() noexcept {
|
2019-11-23 01:13:26 -06:00
|
|
|
auto valerr = foo(1);
|
|
|
|
oxReturnError(valerr);
|
2019-11-26 23:35:24 -06:00
|
|
|
doStuff(valerr.value);
|
2019-11-23 01:13:26 -06:00
|
|
|
return OxError(0);
|
|
|
|
}
|
|
|
|
```
|
2021-03-16 21:16:19 -05:00
|
|
|
Ox also has the ```oxRequire``` 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)
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
```oxRequire```:
|
|
|
|
```cpp
|
2021-10-23 14:10:16 -05:00
|
|
|
ox::Result<int> f() noexcept {
|
2021-03-16 21:16:19 -05:00
|
|
|
// do stuff
|
|
|
|
}
|
|
|
|
|
2021-10-23 14:10:16 -05:00
|
|
|
ox::Result<int> f2() noexcept {
|
2021-04-17 18:15:12 -05:00
|
|
|
oxRequire(i, f()); // const auto [out, oxConcat(oxRequire_err_, __LINE__)] = x; oxReturnError(oxConcat(oxRequire_err_, __LINE__))
|
2021-03-16 21:16:19 -05:00
|
|
|
return i + 4;
|
|
|
|
}
|
|
|
|
```
|
|
|
|
```oxRequire``` is not quite as versatile, but it should still cleanup a lot of otherwise less ideal code.
|
|
|
|
|
2021-04-17 18:15:12 -05:00
|
|
|
```oxRequire``` also has variants for throwing the error and for making to value non-const:
|
|
|
|
|
|
|
|
* ```oxRequireM``` - oxRequire Mutable
|
|
|
|
* ```oxRequireT``` - oxRequire Throw
|
|
|
|
* ```oxRequireMT``` - oxRequire Mutable Throw
|
|
|
|
|
2021-05-03 20:51:56 -05:00
|
|
|
### Logging and Output
|
2021-03-16 21:16:19 -05:00
|
|
|
|
2021-03-18 01:33:29 -05:00
|
|
|
Ox provides for logging and debug prints via the ```oxTrace```, ```oxDebug```, and ```oxError``` macros.
|
|
|
|
Each of these also provides a format variation.
|
2021-03-16 21:16:19 -05:00
|
|
|
|
2021-05-03 20:51:56 -05:00
|
|
|
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.
|
|
|
|
|
2021-03-16 21:16:19 -05:00
|
|
|
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.
|
2021-10-23 14:10:16 -05:00
|
|
|
This makes ```oxDebug``` preferable to other forms of logging, as temporary prints should
|
|
|
|
never be checked in.
|
2021-03-16 21:16:19 -05:00
|
|
|
|
2021-03-18 01:33:29 -05:00
|
|
|
```oxError``` always prints.
|
|
|
|
It includes file and line, and is prefixed with a red "ERROR:".
|
|
|
|
It should generally be used conservatively.
|
|
|
|
It shuld be used only when there is an error that is not technically fatal, but
|
|
|
|
the user almost certainly wants to know about it.
|
|
|
|
|
2021-03-16 21:16:19 -05:00
|
|
|
```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"
|
|
|
|
}
|
|
|
|
```
|
2019-11-23 01:13:26 -06:00
|
|
|
|
2021-03-18 01:33:29 -05:00
|
|
|
```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"
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2020-06-29 11:31:11 -05:00
|
|
|
### File I/O
|
2020-01-24 18:55:29 -06:00
|
|
|
|
2020-06-29 11:31:11 -05:00
|
|
|
All engine file I/O should go through nostalgia::core::Context, which should go
|
2021-02-26 08:55:40 -06:00
|
|
|
through ox::FileSystem. Similarly, all studio file I/O should go thorough
|
2020-06-29 11:31:11 -05:00
|
|
|
nostalgia::studio::Project, which should go through ox::FileSystem.
|
2020-01-24 18:55:29 -06:00
|
|
|
|
2020-06-29 11:31:11 -05:00
|
|
|
ox::FileSystem abstracts away differences between conventional storage devices
|
|
|
|
and ROM.
|
2021-05-03 20:51:56 -05:00
|
|
|
|
|
|
|
### 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
|
2022-02-26 22:52:35 -06:00
|
|
|
can do anything with the data provided to it.
|
2021-05-03 20:51:56 -05:00
|
|
|
|
|
|
|
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>
|
2022-07-12 00:53:36 -05:00
|
|
|
constexpr ox::Error model(T *h, ox::CommonPtrWith<NostalgiaPalette> auto *pal) noexcept {
|
2021-10-23 14:10:16 -05:00
|
|
|
h->template setTypeInfo<NostalgiaPalette>();
|
2022-07-12 00:53:36 -05:00
|
|
|
// it is also possible to provide the type name and type version as function arguments
|
2021-10-23 14:10:16 -05:00
|
|
|
//h->setTypeInfo("net.drinkingtea.nostalgia.core.NostalgiaPalette", 1);
|
|
|
|
oxReturnError(h->field("colors", &pal->colors));
|
2021-05-03 20:51:56 -05:00
|
|
|
return OxError(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
template<typename T>
|
2022-07-12 00:53:36 -05:00
|
|
|
constexpr ox::Error model(T *h, ox::CommonPtrWith<NostalgiaGraphic> auto *ng) noexcept {
|
2021-10-23 14:10:16 -05:00
|
|
|
h->template setTypeInfo<NostalgiaGraphic>();
|
|
|
|
oxReturnError(h->field("bpp", &ng->bpp));
|
|
|
|
oxReturnError(h->field("rows", &ng->rows));
|
|
|
|
oxReturnError(h->field("columns", &ng->columns));
|
|
|
|
oxReturnError(h->field("defaultPalette", &ng->defaultPalette));
|
|
|
|
oxReturnError(h->field("pal", &ng->pal));
|
|
|
|
oxReturnError(h->field("pixels", &ng->pixels));
|
2021-05-03 20:51:56 -05:00
|
|
|
return OxError(0);
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
The model system also provides for unions:
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
|
|
|
#include <ox/model/types.hpp>
|
|
|
|
|
|
|
|
class FileAddress {
|
|
|
|
|
|
|
|
template<typename T>
|
2022-07-12 00:53:36 -05:00
|
|
|
friend constexpr Error model(T*, ox::CommonPtrWith<FileAddress> auto*) noexcept;
|
2021-05-03 20:51:56 -05:00
|
|
|
|
|
|
|
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;
|
|
|
|
};
|
|
|
|
|
2021-10-23 14:10:16 -05:00
|
|
|
protected:
|
2021-05-03 20:51:56 -05:00
|
|
|
FileAddressType m_type = FileAddressType::None;
|
|
|
|
Data m_data;
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
template<typename T>
|
2022-07-12 00:53:36 -05:00
|
|
|
constexpr Error model(T *h, ox::CommonPtrWith<FileAddress::Data> auto *obj) noexcept {
|
2021-10-23 14:10:16 -05:00
|
|
|
h->template setTypeInfo<FileAddress::Data>();
|
2022-07-12 00:53:36 -05:00
|
|
|
oxReturnError(h->fieldCString("path", &obj->path));
|
|
|
|
oxReturnError(h->fieldCString("constPath", &obj->path));
|
2021-10-23 14:10:16 -05:00
|
|
|
oxReturnError(h->field("inode", &obj->inode));
|
2021-05-03 20:51:56 -05:00
|
|
|
return OxError(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
template<typename T>
|
2022-07-12 00:53:36 -05:00
|
|
|
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;
|
|
|
|
oxReturnError(io->field("type", &type));
|
|
|
|
oxReturnError(io->field("data", UnionView(&fa->m_data, 0)));
|
|
|
|
} else {
|
|
|
|
auto type = static_cast<int8_t>(fa->m_type);
|
|
|
|
oxReturnError(io->field("type", &type));
|
|
|
|
fa->m_type = static_cast<FileAddressType>(type);
|
|
|
|
oxReturnError(io->field("data", UnionView(&fa->m_data, static_cast<int>(fa->m_type))));
|
|
|
|
}
|
2021-05-03 20:51:56 -05:00
|
|
|
return OxError(0);
|
|
|
|
}
|
2022-07-12 00:53:36 -05:00
|
|
|
|
2021-05-03 20:51:56 -05:00
|
|
|
```
|
|
|
|
|
2022-02-26 22:52:35 -06:00
|
|
|
There are also macros in ```<ox/model/def.hpp>``` for simplifying the declaration of models:
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
oxModelBegin(NostalgiaGraphic)
|
|
|
|
oxModelField(bpp)
|
|
|
|
oxModelField(rows)
|
|
|
|
oxModelField(columns)
|
|
|
|
oxModelField(defaultPalette)
|
|
|
|
oxModelField(pal)
|
|
|
|
oxModelField(pixels)
|
|
|
|
oxModelEnd()
|
|
|
|
```
|
|
|
|
|
2021-05-03 20:51:56 -05:00
|
|
|
### 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.
|
|
|
|
|
2023-12-12 20:17:08 -06:00
|
|
|
These formats do not currently support floats.
|
|
|
|
|
2021-05-03 20:51:56 -05:00
|
|
|
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
|
|
|
|
|
2023-12-12 20:17:08 -06:00
|
|
|
Except when the data is exported for loading on the GBA, Claw is always used as
|
|
|
|
a wrapper around the bare formats.
|
|
|
|
|
2021-05-03 20:51:56 -05:00
|
|
|
#### Metal Claw Example
|
|
|
|
|
|
|
|
##### Read
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
#include <ox/mc/read.hpp>
|
|
|
|
|
|
|
|
ox::Result<NostalgiaPalette> loadPalette1(const Buffer &buff) noexcept {
|
|
|
|
return ox::readMC<NostalgiaPalette>(buff);
|
|
|
|
}
|
|
|
|
|
|
|
|
ox::Result<NostalgiaPalette> loadPalette2(const Buffer &buff) noexcept {
|
|
|
|
return ox::readMC<NostalgiaPalette>(buff.data(), buff.size());
|
|
|
|
}
|
|
|
|
|
|
|
|
ox::Result<NostalgiaPalette> loadPalette3(const Buffer &buff) noexcept {
|
|
|
|
NostalgiaPalette pal;
|
2022-07-12 00:53:36 -05:00
|
|
|
std::size_t sz = 0;
|
|
|
|
oxReturnError(ox::readMC(buff.data(), buff.size(), &pal, &sz));
|
|
|
|
buffer.resize(sz);
|
2021-05-03 20:51:56 -05:00
|
|
|
return pal;
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
##### Write
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
#include <ox/mc/write.hpp>
|
|
|
|
|
|
|
|
ox::Result<ox::Buffer> writeSpritePalette1(NostalgiaPalette *pal) noexcept {
|
|
|
|
ox::Buffer buffer(ox::units::MB);
|
2022-07-12 00:53:36 -05:00
|
|
|
std::size_t sz = 0;
|
|
|
|
oxReturnError(ox::writeMC(buffer.data(), buffer.size(), pal, &sz));
|
|
|
|
buffer.resize(sz);
|
2023-11-23 18:22:22 -06:00
|
|
|
return buffer;
|
2021-05-03 20:51:56 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
ox::Result<ox::Buffer> writeSpritePalette2(NostalgiaPalette *pal) noexcept {
|
|
|
|
return ox::writeMC(pal);
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
#### Organic Claw Example
|
|
|
|
|
|
|
|
##### Read
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
#include <ox/oc/read.hpp>
|
|
|
|
|
|
|
|
ox::Result<NostalgiaPalette> loadPalette1(const Buffer &buff) noexcept {
|
|
|
|
return ox::readOC<NostalgiaPalette>(buff);
|
|
|
|
}
|
|
|
|
|
|
|
|
ox::Result<NostalgiaPalette> loadPalette2(const Buffer &buff) noexcept {
|
|
|
|
return ox::readOC<NostalgiaPalette>(buff.data(), buff.size());
|
|
|
|
}
|
|
|
|
|
|
|
|
ox::Result<NostalgiaPalette> loadPalette3(const Buffer &buff) noexcept {
|
|
|
|
NostalgiaPalette pal;
|
|
|
|
oxReturnError(ox::readOC(buff.data(), buff.size(), &pal));
|
|
|
|
return pal;
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
##### Write
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
#include <ox/oc/write.hpp>
|
|
|
|
|
|
|
|
ox::Result<ox::Buffer> writeSpritePalette1(NostalgiaPalette *pal) noexcept {
|
|
|
|
ox::Buffer buffer(ox::units::MB);
|
|
|
|
oxReturnError(ox::writeOC(buffer.data(), buffer.size(), pal));
|
2023-11-23 18:22:22 -06:00
|
|
|
return buffer;
|
2021-05-03 20:51:56 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
ox::Result<ox::Buffer> writeSpritePalette2(NostalgiaPalette *pal) noexcept {
|
|
|
|
return ox::writeOC(pal);
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
#### Claw Example
|
|
|
|
|
|
|
|
##### Read
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
#include <ox/claw/read.hpp>
|
|
|
|
|
|
|
|
ox::Result<NostalgiaPalette> loadPalette1(const Buffer &buff) noexcept {
|
|
|
|
return ox::readClaw<NostalgiaPalette>(buff);
|
|
|
|
}
|
|
|
|
|
|
|
|
ox::Result<NostalgiaPalette> loadPalette2(const Buffer &buff) noexcept {
|
|
|
|
return ox::readClaw<NostalgiaPalette>(buff.data(), buff.size());
|
|
|
|
}
|
|
|
|
|
|
|
|
ox::Result<NostalgiaPalette> loadPalette3(const Buffer &buff) noexcept {
|
|
|
|
NostalgiaPalette pal;
|
|
|
|
oxReturnError(ox::readClaw(buff.data(), buff.size(), &pal));
|
|
|
|
return pal;
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
##### Write
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
#include <ox/claw/write.hpp>
|
|
|
|
|
|
|
|
ox::Result<ox::Buffer> writeSpritePalette(NostalgiaPalette *pal) noexcept {
|
|
|
|
return ox::writeClaw(&pal);
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|