[nostalgia] Reorganize developer handbook and add section on file I/O

This commit is contained in:
Gary Talent 2020-06-29 11:31:11 -05:00
parent e91e9ad58f
commit 95e752bf3b

View File

@ -54,6 +54,72 @@ All components have a platform indicator next to them:
* Pointer and reference designators should be bound to the identifier name and
not the type, unless there is not identifier name.
### 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.
The C++ language designers aren't stupid, trust them or question them, but
don't ignore them.
#### Casting
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.
#### Library Usage
C++ libraries should generally be preferred to C libraries. C libraries are
allowed, but pay extra attention.
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);
// new can technically throw, though this project considers out-of-memory to be unrecoverable
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);
// free does not call the destructor, which causes memory leak for array inside list
free(list);
return 0;
}
```
The code base where this was observed actually got away with this for the most
part, as the std::vector implementation used evidentally waited until the
internal array was needed before initializing and the memory was zeroed out
because the allocation occurred early in the program's execution. While the
std::vector implementation in queston worked with this code and the memory leak
is not noticable 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 instatiations of the classes in
question.
### Pointers vs References
Pointers are generally preferred to references. References should be used for
@ -157,68 +223,11 @@ void studioCode() {
}
```
### Write C++, Not C
### File I/O
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.
The C++ language designers aren't stupid, trust them or question them, but
don't ignore them.
All engine file I/O should go through nostalgia::core::Context, which should go
through ox::FileSystem. Similarly, all studio file I/O should go throuh
nostalgia::studio::Project, which should go through ox::FileSystem.
#### Casting
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.
#### Library Usage
C++ libraries should generally be preferred to C libraries. C libraries are
allowed, but pay extra attention.
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);
// new can technically throw, though this project considers out-of-memory to be unrecoverable
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);
// free does not call the destructor, which causes memory leak for array inside list
free(list);
return 0;
}
```
The code base where this was observed actually got away with this for the most
part, as the std::vector implementation used evidentally waited until the
internal array was needed before initializing and the memory was zeroed out
because the allocation occurred early in the program's execution. While the
std::vector implementation in queston worked with this code and the memory leak
is not noticable 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 instatiations of the classes in
question.
ox::FileSystem abstracts away differences between conventional storage devices
and ROM.