Athena's primary taskflow involves use of I/O stream objects to convey
data to and from a binary medium. The stream objects are used to iteratively
read bytes from the data source and convert them into usable data-primitives
(i.e. integers, floats, SIMD vectors).
I/O Streaming
I/O streams have a common virtual interface in Athena, so code that consumes or
emits data is written against athena::io::IStreamReader and/or
athena::io::IStreamWriter. Projects may use type aliasing to
make Athena's namespacing more convenient for projects that use it heavily.
Assume the following example data class:
DemoRecord.hpp
The implementations for the I/O methods look like this:
ReadDemo.cpp
WriteDemo.cpp
Built-in Stream Backends
Athena ships with some useful streaming backends to get common
data interfaces up and going.
File Streams (with built-in memory buffering)
ReadDemoFile.cpp
WriteDemoFile.cpp
Memory Streams (application-owned buffer)
ReadDemoMem.cpp
WriteDemoMem.cpp
Memory Copy Streams (stream-owned copy buffer)
ReadDemoMemCopy.cpp
WriteDemoMemCopy.cpp
Custom Stream Backends
The actual data interfacing occurs via raw-byte transfers using
IStreamReader::readUBytesToBuf() and
IStreamWriter::writeUBytes(). Programs needing to interface
with custom data streams do so by inheriting IStreamReader/Writer and
implementing a few methods.
MyReadBackend.hpp
MyWriteBackend.hpp
DNA
Loading data field-by-field with Athena's Core has some benefits: It provides
a sensible place to convert endian (byte-order) for numeric types.
It can be made to handle compound types. The backing buffer is
tightly-packed for compact storage (no need to worry about
SIMD-alignment once read).
The major drawback is inconvenience; code needs to be written to
map the data structure's fields to segments in the streamed buffer.
ATDNA: Athena's Copilot
Athena ships with a build-tool inspired by
Blender's build system
called atdna. This tool transforms C++ record
declarations (i.e. structs, classes, unions) into reader/writer implementations
automatically. When properly integrated into a project's build system,
changes made to the C++ records will trigger these implementations to update
along with the project.
Special template types are provided by athena::io::DNA to
produce a well-defined DNA Record.
DNADemo.hpp
Once the record has been defined in a header file, the header is passed
to atdna whenever it changes. It uses
libclang
to decompose the header into C++ declarations and emits the appropriate
read/write functions according to the field types.
ATDNA + CMake
Currently, ATDNA is easiest to integrate using CMake.
Projects may define DNA targets using the atdna(<out> <in>) macro, and
connecting the output file to a library or executable target.
CMake integrates with several build environments including make, Visual Studio,
and Xcode. Please see CMake's documentation
for details.
CMakeLists.txt
When built, a file like the following is generated:
DNADemo.cpp
All together now!
Once the read/write implementations are compiled in, the application may invoke
them however's convenient:
main.cpp
main.cpp
YAML
Having a uniform system to interchange binary data is nice, but we musn't forget
the human programmers that work with the data and design systems around it.
This is where having a textual representation of the structured data is convenient.
YAML is a simplistic data-serialization
format, capable of organizing string-representations of data members into mappings and
sequences with multiple levels of hierarchy.
athena::io::DNA has been subclassed as athena::io::DNAYaml
to have ATDNA generate YAML serialization/deserialization alongside the binary readers/writers.
It's used just like the DNA system from the developer's perspective.
YAMLDemo.hpp
Now applications can use YAML as a data source/destination in addition to the
original binary format the DNA is based on. Such YAML may look like this:
YAMLDemo.yaml
All together now!
The YAML implementations are compiled side-by-side with the DNA implementations.
The application may invoke them in a similar manner: