Skip to content

Commit

Permalink
cleanup
Browse files Browse the repository at this point in the history
I'd like to think this commit marks the end of the first phase
of ANESE's development. It's been a long road, but ANESE is
finally at a point where I'm mostly happy with it.

ANESE sure as hell isn't perfect, but hey, it's pretty good!
The core code is pretty clean, the UI code is... acceptable, but
most importantly, ANESE actually plays my favorite NES games!

There is still some work to be done before I'd be comfortable
giving ANESE a v1.0.0 release, but what I have here is still
pretty great.

Let's call this ANESE v0.9.0 :)

-------

So, what next?

Well, contrary to what I said in some earlier commits, I think i'll
continue to work on ANESE a little bit more!
Specifically, i'd like to rewrite the 6502 emulation.

My current implementation is... okay.
It's instruction-level cycle accurate, but I don't think that's
good enough. It really should be sub-instruction level accurate.
Odds are the added accuracy will fix _a lot_ of bugs.

Aside from accuracy though, I have another reason to do a rewrite...

As a Waterloo student, I have to do a Work Term Report on some
technical project i've worked on recently. ANESE is one such project.
Since the report isn't designed to be very long, I'd limit my scope
to just a small aspect of ANESE: the 6502 emulator.

Yes, I could just write the report on how I arrived at my current
implementation, but I think it would be cool to attempt a cleaner
rewrite, and compare and contrast the two versions.

So yeah, stay tuned! I might also post the writeup (once I get
around to it)
  • Loading branch information
daniel5151 committed Jul 4, 2018
1 parent 52c8ea0 commit 8423bd9
Show file tree
Hide file tree
Showing 28 changed files with 382 additions and 426 deletions.
72 changes: 41 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,20 @@
</p>

**ANESE** (**A**nother **NES** **E**mulator) is a Nintendo Entertainment System
Emulator being written for fun and learning.
Emulator written for fun and learning.

While accuracy and performance are long-term goals, ANESE's primary focus is
getting some of the more popular titles up and running. Most basic Mappers have
While accuracy and performance are long-term goals, ANESE's primary focus is to
get some of the more popular titles up and running. Most basic Mappers have
been implemented, so many popular titles should be working! :smile:

ANESE is built with _cross-platform_ in mind, and is regularly built on all
ANESE is built with _cross-platform_ in mind, and is regularly tested on all
major platforms (macOS, Windows, and Linux). ANESE doesn't use any
vendor-specific language extensions, and is compiled with strict compiler flags.
It is also linted (fairly) regularly.

Lastly, ANESE strives to keep a clean and _interesting_ C++11 codebase,
emphasizing _readability_, _maintainability_, and _approachability_. The code
is heavily commented, providing sources and insights for much of the logic.
is well commented, providing sources and insights for much of the logic.

## Downloads

Expand Down Expand Up @@ -87,12 +87,13 @@ If you're interested in looking under the hood of the PPU, you can pass the

## Running

ANESE can run from the shell using `anese [rom.nes]` syntax.
Running ANESE with no arguments throws you into a directory-browser, from which
you can navigate to your ROM and launch it.

If no ROM is provided, a simple dialog window pops-up prompting the user to
select a valid NES rom.

For a full list of switches, run `anese -h`
Alternatively, ANESE can run from the shell using `anese [rom.nes]` syntax.
Certain features are only accessible from the command-line at the moment (e.g:
movie recording / playback, PPU timing hacks). For a full list of switches,
run `anese -h`

**Windows Users:** make sure the executable can find `SDL2.dll`! Download the
runtime DLLs from the SDL website, and plop them in the same directory as
Expand Down Expand Up @@ -163,17 +164,28 @@ that rely on sub-instruction level timings (eg: Solomon's Key).

## TODO

This is a list of things I would like to try to accomplish, with those closer to
the top higher on my priority list:
These are features that will add major value to ANESE:

- [ ] _Implement_: Cycle accurate CPU (will probably fix _many_ bugs)
- [ ] _Implement_: Better menu (not just fs, also config)
- [ ] _CMake_: more robust macOS bundles (good way to get SDL2.0 packaged?)
- [ ] _Implement_: LibRetro Core
- [ ] _Implement_: Get the Light-gun working
- [ ] _Debugging_: Add debug GUI
- All objects implementing the Memory interface _must also_ implement `peek`,
i.e: a `const` read. As such, a debugger could easily inspect any/all memory
locations with no side effects!

Here's a couple that have been crossed off already:

- [x] _Implement_: My own APU (don't use Blarrg's)
- [ ] _Implement_: More robust menu system
- [x] _Refactor_: Modularize `main.cc` - push everything into `src/ui/`
- [x] _Refactor_: Split `gui.cc` into more files!
- [ ] _CMake_: Make building macOS bundles less brittle
- [x] _Refactor_: Push common mapper behavior to Base Mapper (eg: bank chunking)
- [ ] _Implement_: LibRetro Core
- [ ] _Implement_: Sub-instruction cycle accurate CPU

And here are some ongoing low-priority goals:

- [ ] _Refactor_: Roll-my-own Sound_Queue (SDL_QueueAudio?)
- [ ] _Cleanup_: Unify naming conventions (either camelCase or snake_case)
- [ ] _Cleanup_: Comment the codebase _even more_
- [ ] _Security_: Actually bounds-check files lol
Expand Down Expand Up @@ -221,7 +233,7 @@ the top higher on my priority list:
- [x] DMC DMA
- [ ] Joypads
- [x] Basic Controller
- [ ] Zapper - _needs work_
- [ ] Zapper - _still needs work_
- [ ] NES Four Score

### Secondary Milestones
Expand All @@ -234,13 +246,13 @@ the top higher on my priority list:
- [x] Battery Backed RAM - Saves to `.sav`
- [x] Save-states
- [ ] Dump to file
- [ ] Config File
- [x] Config File
- [x] Preserve ROM path
- [x] Window size
- [ ] Controls
- [x] Running NESTEST (behind a flag)
- [x] Controller support - _currently very basic_
- [ ] A SDL GUI
- [x] A SDL GUI
- [x] SDL-based ROM picker
- [ ] Options menu

Expand All @@ -257,13 +269,9 @@ the top higher on my priority list:
- [x] SDL Standalone
- [ ] LibRetro
- [ ] Debugger!
- There is a lot of great infrastructure in place that could make ANESE a
top-tier NES debugger, primarily the fact that all memory-interfaced
objects _must_ implement `peek`, which enables non-destructive looks at
arbitrary objects!
- [ ] CPU
- [ ] Step through instructions
- [ ] PPU Views
- [x] PPU Views
- [x] Static Palette
- [x] Palette Memory
- [x] Pattern Tables
Expand All @@ -273,25 +281,27 @@ the top higher on my priority list:
### Accuracy & Compatibility

- More Mappers! Always more mappers!
- [ ] Add automatic testing
- [ ] Screenshots: compare power-on with 30 seconds of button mashing
- [ ] Test ROMs: Parse debug outputs
- CPU
- [ ] Implement Unofficial Opcodes
- [ ] Pass More Tests yo
- [ ] Pass More Tests
- [ ] _\(Stretch\)_ Switch to sub-instruction level cycle-based emulation
(vs instruction level)
- PPU
- [x] Make the sprite rendering pipeline more accurate (fetch-timings)
- This _should_ fix _Punch Out!!_ **UPDATE:** it totally did.
- [ ] Pass More Tests yo
- [ ] Make value in PPU <-> CPU bus decay
- [ ] Pass More Tests
- [ ] Make value in PPU <-> CPU bus decay?

## Attributions

- A big shout-out to [LaiNES](https://github.com/AndreaOrru/LaiNES) and
[fogleman/nes](https://github.com/fogleman/nes), two solid NES emulators that I
referenced while implementing some particularly tricky parts of the PPU). While
I actively avoided looking at the source codes of other NES emulators as I wrote
my initial implementations of the CPU and PPU, I did occationally sneak a peek
at how others did things when I got very stuck.
I actively avoided looking at the source codes of other NES emulators while
writing my initial implementations of the CPU and PPU, I did sneak a peek at how
others solved some problems once I got stuck.
- These awesome libraries make ANESE a lot nicer to use:
- [sdl2](https://www.libsdl.org/) - A/V and Input
- [SDL_inprint](https://github.com/driedfruit/SDL_inprint/) - SDL fonts, without SDL_ttf
Expand Down
5 changes: 3 additions & 2 deletions roms/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
All these test roms are sourced from http://wiki.nesdev.com/w/index.php/Emulator_tests
and are subject to the terms of usage laid out by their respective authors.
The roms under `tests` are primarily sourced from
http://wiki.nesdev.com/w/index.php/Emulator_tests
and are subject to the licenses they were initially distributed under.

They are mirrored in this repository for personal convenience.
3 changes: 3 additions & 0 deletions scripts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
These scripts should be run from the root directory.

eg: `sh scripts/cppcheck.sh`
File renamed without changes.
File renamed without changes.
File renamed without changes.
94 changes: 15 additions & 79 deletions src/common/serializable.cc
Original file line number Diff line number Diff line change
Expand Up @@ -117,25 +117,25 @@ Serializable::Chunk* Serializable::serialize() const {
: (field.type == 2 ? *field.len_variable : field.len_fixed)
);
switch (field.type) {
case _field_type::INVALID: assert(false); break;
case _field_type::POD:
case _field_type::SERIAL_INVALID: assert(false); break;
case _field_type::SERIAL_POD:
fprintf(stderr, "0x%08X\n", *((uint*)field.thing));
next = new Chunk(field.thing, field.len_fixed);
break;
case _field_type::ARRAY_VARIABLE:
case _field_type::SERIAL_ARRAY_VARIABLE:
fprintf(stderr, "0x%08X\n", **((uint**)field.thing));
next = new Chunk(*((void**)field.thing), *field.len_variable);
break;
case _field_type::SERIALIZABLE:
case _field_type::SERIAL_IZABLE:
fprintf(stderr, "serializable: \n");
next = ((Serializable*)field.thing)->serialize();
assert(next != nullptr);
break;
case _field_type::SERIALIZABLE_PTR: {
case _field_type::SERIAL_IZABLE_PTR: {
fprintf(stderr, "serializable_ptr: ");
if (!field.thing) {
fprintf(stderr, "null\n");
next = new Chunk(); // nullchunk == tried to serialize null serializable
next = new Chunk(); // nullchunk.
} else {
fprintf(stderr, "recursive\n");
next = ((Serializable*)field.thing)->serialize();
Expand All @@ -151,8 +151,8 @@ Serializable::Chunk* Serializable::serialize() const {
tail = next;
}

if (field.type == _field_type::SERIALIZABLE ||
field.type == _field_type::SERIALIZABLE_PTR) {
if (field.type == _field_type::SERIAL_IZABLE ||
field.type == _field_type::SERIAL_IZABLE_PTR) {
while (tail->next) tail = tail->next;
}
}
Expand Down Expand Up @@ -180,38 +180,32 @@ const Serializable::Chunk* Serializable::deserialize(const Chunk* c) {
: (field.type == 2 ? *field.len_variable : field.len_fixed)
);

if (c->len != field.len_fixed && (field.type == _field_type::ARRAY_VARIABLE && c->len != *field.len_variable)) {
fprintf(stderr, "[Deserialization] Field length mismatch! "
"This is probably caused by a change in serialization order.\n");
assert(false);
}

switch (field.type) {
case _field_type::INVALID: assert(false); break;
case _field_type::POD:
case _field_type::SERIAL_INVALID: assert(false); break;
case _field_type::SERIAL_POD:
fprintf(stderr, "0x%08X\n", *((uint*)c->data));
memcpy(field.thing, c->data, c->len);
c = c->next;
break;
case _field_type::ARRAY_VARIABLE:
case _field_type::SERIAL_ARRAY_VARIABLE:
fprintf(stderr, "0x%08X\n", *((uint*)c->data));
memcpy(*((void**)field.thing), c->data, c->len);
c = c->next;
break;
case _field_type::SERIALIZABLE:
case _field_type::SERIAL_IZABLE:
fprintf(stderr, "serializable: \n");
// recursively deserialize the data
c = ((Serializable*)field.thing)->deserialize(c);
break;
case _field_type::SERIALIZABLE_PTR: {
case _field_type::SERIAL_IZABLE_PTR: {
fprintf(stderr, "serializable_ptr: ");
if (c->len == 0 && field.thing == nullptr) {
fprintf(stderr, "null\n");
// nullchunk == this serializable was null, so the thing must be null
// nullchunk. Ignore this and carry on.
c = c->next;
} else {
// recursively deserialize the data
fprintf(stderr, "recursive\n");
// recursively deserialize the data
c = ((Serializable*)field.thing)->deserialize(c);
}
} break;
Expand All @@ -222,61 +216,3 @@ const Serializable::Chunk* Serializable::deserialize(const Chunk* c) {
delete field_data;
return c;
}

// // "testing"
// struct test_struct_base : public Serializable {
// uint balls;
// };

// struct test_struct : test_struct_base {
// uint val;
// test_struct* next;
// uint val2;

// u8* varlen_array;

// SERIALIZE_START(4, "test")
// SERIALIZE_SERIALIZABLE_PTR(this->next)
// SERIALIZE_POD(this->val)
// SERIALIZE_POD(this->val2)
// SERIALIZE_ARRAY_VARIABLE(this->varlen_array, this->val)
// SERIALIZE_END(4)

// test_struct(uint val) {
// this->val = val;
// this->val2 = val << 16;
// this->next = nullptr;
// this->varlen_array = new u8 [val];
// for (uint i = 0; i < val; i++) {
// this->varlen_array[i] = (i + 1) * 2;
// this->varlen_array[i] |= this->varlen_array[i] << 4;
// }
// }

// void add(test_struct* next) { this->next = next; }
// };

// static auto test = [](){
// test_struct* basicboi = new test_struct(1);
// basicboi->add(new test_struct(2));
// basicboi->next->add(new test_struct(3));

// const u8* data;
// uint len;
// Serializable::Chunk* base_state = ((test_struct_base*)basicboi)->serialize();
// base_state->debugprint();
// base_state->collate(data, len);

// basicboi->val = 100;
// basicboi->next->val = 200;
// basicboi->next->next->val = 300;

// const Serializable::Chunk* too = Serializable::Chunk::parse(data, len);
// too->debugprint();

// ((test_struct_base*)basicboi)->deserialize(Serializable::Chunk::parse(data, len));

// assert(basicboi->val == 1 && basicboi->next->val == 2 && basicboi->next->next->val == 3);

// return 1;
// }();
Loading

0 comments on commit 8423bd9

Please sign in to comment.