Hacker News new | past | comments | ask | show | jobs | submit login
ProjectPSX – A C# coded emulator of the original Playstation (github.com)
174 points by eatonphil 7 days ago | hide | past | web | favorite | 57 comments
 help




> ProjectPSX dosn't use any external dependency and uses rather simplistic C# code.

Very impressive. This makes it more interesting to see how the drawing code and the controls were implemented in addition to the possibility of it being cross-platform if it using .NET Core.


It uses System.Windows.Forms and friends. It is a Windows app.

It has like 50 lines of code that are actually platform dependent, from what I can see.

Should be straightforward to port to GTK# or Eto.Forms or something, if one had the inclination.


https://www.mono-project.com/docs/gui/winforms/ might allow this to be ported easily though.

winforms on mono never really worked, and hasn't been updated in about 12 years https://github.com/mono/winforms

While I'm a novice - Looking at this, it doesn't appear to require any porting.

Mesen (for NES) is a good example of a cross-platform C# emulator with a comprehensive debugger UI: https://www.mesen.ca/

(As long as you have Windows or Linux, that is...)


As a day-to-day C# developer, I'm curious about a few things:

- Do you ever find that the Garbage Collector causes noticeable pausing in the emulator?

- Did you do this with 100% managed memory, or do you need to use unsafe code / real pointers?

(Apologies that I didn't read the source code.)


Hello There, I'm the owner of this repo. So nice that has ended here somehow. Gonna answer the upper quests...

- Do you ever find that the Garbage Collector causes noticeable pausing in the emulator?

The GC dosn't cause noticeable pausing on my setup. Even tho it could allocate less.

- Did you do this with 100% managed memory, or do you need to use unsafe code / real pointers?

In fact it was done without unsafe code or pointers till some of the latest commits. Where i switched to unsafe because performance reasons. On my setup that was arround 10 fps more.


Incredible work, congrats!

Search the repo for "new" and you'll see that everything is allocated at construction time. Everything hangs off the window class so essentially all memory is allocated as the project starts up. You'd probably want to pre-allocate in any language because but this also relieves GC pressure.

Another thing you can do in C# is leverage stack allocation using structs.


Relatedly, the new .NET Span<T> APIs open up a lot more opportunities for stack allocation and "pointer arithmetic-style" work in "safe" code. Span<T> is really fascinating at the performance opportunities it opens in .NET (and real performance gains already achieved in, for instance, ASP.NET Core).

I skimmed the code a little and did a few Github searches on it and it looks like the only unsafe blocks of code are in the core data bus. It's not entirely clear why it was done that way, as the CPU itself is using managed arrays to represent memory. Perhaps it was necessary for speed, as it seems primarily concerned with loading the executables that are to be emulated.

Otherwise, everything looks statically allocated. So there should be no GC collections at all.


A rather useful pattern in C# is to load a blob into a byte array, then inside unsafe code, typecast it to a pointer to well-defined struct.

There's also managed ways to do that, but they are slower and involve copying memory.


Any chance you have an example of this pattern you can point to? Also, would the use of Span<T> make it possible to do this without unsafe code?

I use it with DeviceIOControl for interoperating with a driver.

I didn't learn by following someone's example, in my case, I have to work with so many different structs in a high performance use case that I took the time to re-learn pointers. (Haven't used them in years.)

1: Memory is allocated using new byte[bufferSize]

2: That byte[] is pinned to a byte* or void* via fixed

3: The pointer is passed to DeviceIOControl

4: Traditional c-style typecasting inside of fixed

5: Traditional GC cleans up your byte[]. This avoids problems with managed programs that allocate lots of native memory.

I don't know how to use pointers with Span<T>...

BUT: I do similar things with ArraySegment<byte[]>. All that's needed is pointer arithmetic:

ArraySegment<byte> arraySegment = ...

fixed (byte* dataArrayPtr = arraySegment.Array)

{

   var dataPtr = dataArrayPtr + arraySegment.Offset;

   var ptr = (SomeStruct*)dataPtr;

}

I recently learned about MemoryMappedFiles in .NET. https://docs.microsoft.com/en-us/dotnet/standard/io/memory-m...

I only mention it because I'm not entirely clear on what is going on or how it works (largely because I tend to only be able to learn things by doing them, which is my own issue, not your failure to describe it), but after reading the docs on MemoryMappedFiles, I wonder if it's something that could be useful to you.


I haven't read the code, but you usually don't let the garbage collector get a chance to do anything with projects where it matters.

In fact, the techniques for C# here would be pretty much the same as for C++ or C: Don't do dynamic allocation in the hot path. And if you must, keep the lifetime short so it gets cleaned up predictably (usually in gen0).

However, for an ancient platform on modern hardware I guess the per-frame budget is plentiful even for an emulator.


Yeah, which is the problem with GC'd languages because it isn't always obvious where allocation will occur.

In what concerns .NET there are Visual Studio plugins that mark exactly that.

D also has something similar as compiler switch.


In what case isn't it obvious? C# makes it quite clear, you allocate to memory on the heap whenever you see the "new" keyword or whenever you see a new closure (because closures are of course just a poor man's objects).

Not necessarily just those things. A foreach loop might have a heap allocation, async/await almost certainly, calling a method with a params[] parameter as well. Everything you call from the BCL might allocate, too and it's not always visible or obvious.

That being said, it is still doable to avoid allocations and the standard library has become much better with not allocating unless really necessary. A lot of the newer things with Span<T> enable zero-allocation usage of certain APIs that before would have allocated.


And even if you use structs with the goal of avoiding allocations, the language doesn't prevent you from accidentally writing code that upcasts them to `object` (eg by calling anything that the struct inherits from `object` and isn't overridden by the struct itself.) This transparently copies the struct to a heap allocation and negates your intent.

This is not entirely true as of C# 7.2. That version of the language adds "ref structs" - which can only be allocated on the stack and cannot be boxed [1].

This requires some care though, as the struct may still be copied needlessly if it is not also marked as `readonly` [2].

[1] - https://blogs.msdn.microsoft.com/mazhou/2018/03/02/c-7-serie... [2] - https://docs.microsoft.com/en-us/dotnet/csharp/write-safe-ef...


Typically, you choose where to optimize after collecting metrics.

The .Net garbage collector is a highly optimized generational garbage collector. Practices like pre-allocating and pooling are discouraged, because most of the time they are premature optimization.

In general, the best way to think about it is to pretend that the garbage collector is a built-in library for pooling and reusing objects.

If, early in your project's lifecycle, you suspect that you'll need to implement your own object pool, just start with stub "Get/Recycle" methods that call new / no-op. You can always swap in a pool later.


This is great. It would be almost perfect if it didn't rely on WinForms (1) and was built on .NET core from the ground up. Anyway, great project I will keep checking out.

(1) instead it could be a runtime that just gives you an interface to the frame buffer somehow and it's up to the integrator to use WinForms/WPF/Qt etc.


That's a very nice idea. I went with WinForms as it was the "stupid easy way" infact on the first i just updated a PictureBox.

There was a time when i had a SDL multiplatform branch but it was noticeably slower so i didn't put much effort on it and it ended deleted.

I mainly focused on the core PSX things and not much on the program itself like the emulator window itself...


>> I mainly focused on the core PSX things and not much on the program itself like the emulator window itself...

As you should. Core functionality always comes first. UI can always be messed about with after, when it 'just works'.

Also, fantastic work, here! :)


I think SDL2-CS[0] would be an awesome candidate for a platform-independent implmentation. Gives you a ton of low-level control.

[0] https://github.com/flibitijibibo/SDL2-CS


It would conflict with this, though:

> ProjectPSX dosn't use any external dependency and uses rather simplistic C# code.

And for a learning project I think the ability to download the code and get it to run immediately without hunting for dependencies, is a very important property. More important I'd say than complicating the code to make it run everywhere.


A platform independent desktop UI framework really is the biggest gap in the .NET platform right now, and I think Microsoft knows it. We'll probably see something new on that front soon, I'd wager.

Microsoft for two BUILD conferences now has stated putting more weight into Xamarin tooling for desktop as well as mobile (WPF, Cocoa, and Gtk renderers for Xamarin) is an end goal. They exist as community projects already (find them all on GitHub), and it continues to sound like Microsoft only expects to invest in them so much as they find community interests, and maybe a reason we don't hear a lot of people talking about those projects is there sadly isn't enough non-Microsoft community interest in such tools.

I hope. But I don't hold my breath before .NET 5. 3.0 is all about getting Windows Desktop scenarios to .NET Core. Not many of the people who require those would benefit from a cross-platform framework, as neither Windows Forms nor WPF would be portable. Avalonia might have the chance of becoming a decent option, but even working for an employer that provides GUI controls for various platforms it's too early to decide to try supporting it. And then there's the problem that Desktop apps are kinda dying out ...

Except 2-1 laptops are also desktops, which many seem to keep forgetting.


> We'll probably see something new on that front soon

MS has no interest in this space [0]. Your best bet at this moment is Electron or other open source alternates like eto forms.

[0] - Scott hunter's .net rocks podcast. The related commentary is past 30 mins from beginning of the show - https://www.dotnetrocks.com/?show=1634


That is outdated.

They were running a developer survey about that

I bet our outcries are getting too loud in Redmond. Similar to those of us not bothering with Core until key frameworks got ported.

https://devblogs.microsoft.com/dotnet/calling-all-net-deskto...


They made a survey but that doesn’t mean they will listen to the feedback.

Windows Forms, WPF, C++/CLI, EF 6, .NET Native, WinUI kind of prove that they do, even if it takes a while to actually pay attention.

Even in regards to WCF, I am not sure if they won't be forced to provide some gRPC migration path, when some Fortune 500 start to complain rather loudly.


> when some Fortune 500 start to complain rather loudly

Not sure it’s such a huge deal, .NET core already has the most complex parts of the WCF. It has fast async TCP and named pipe streams. It has .NET binary XML support, DataContractSerializer, XmlDictionaryReader, XmlDictionaryWriter classes, technically IMO better than protocol buffers: no foreign languages, similar level of performance, convertable to/from text XML if you want a human readable format for easier debugging.

When I needed RPC server in a Linux app written in .NET core, I wrote my own WCF-like thing. I’ve made a T4 template which generates requests/response & envelope classes, and channel factories, using compile-time reflection (EnvDTE stuff) of my service contracts. Took me a few hours. These Fortune 500 companies can probably afford doing the same when they need RPC in .NET.


Nope, usually IT is a cost center, it isn't the main business of the Fortune 500 and if it works, has been battle tested in production, there are zero reasons to rewrite it, just because someone is feeling modern without offering a painless migration path.

I wouldn't be surprised, now that Microsoft owns Electron, if the new UI framework took a form similar to Electron, but I am sure Microsoft will need to have something with close integration for C#/VB development.

What do you mean microsoft owns Electron, looks all open source https://github.com/ElectronNET/Electron.NET the guy happens to work at microsoft?? can you clarify?

That is someone's port of Electron to .NET, the official Electron uses JS (and Chrome). Electron is open source, but was created by GitHub, which Microsoft bought.

https://en.wikipedia.org/wiki/Electron_(software_framework)


Ahh ok, thank you for clarifying that, I was rather confused about it all. I didn't know much about the project by I managed to invent a wrong memory that it was always a C# thing, not that insanely bonkers js, node, applications are webpages thing :)


This one is fascinating - especially the slides from the JavaOne presentation. It JITs heavily-used R3000 blocks into Java bytecode, which is then JITted by the Java interpreter. And he did it 14 years ago!

Having a problem finding the "one line" version. Mind directly linking to it?

https://github.com/kilograham/jpsx/tree/master/src


I think you miss read his comment

Great job! We need more such examples in managed languages.

Very, very cool. Good job! I remember tackling emulating the 6502 was a crazy learning experience, but I did that in C++, but definitely there is value in emulating something this complex in C#. Did you choose emulation because it was interesting or it just seemed to provide the best learning environment for what you were looking for? And have you ever dabbled in PSP emulation? I tried my hands years ago and still have a PSP sitting around with some homebrew I made.

This is great! I've often wondered how emulators are actually written - I understand the theory but it was great to see this, and the linked other emulators this person has also worked on (chip8, gameboy etc).

Super nice and simple to read - e.g. cpu.cs has the thing that does the actual "MUL" and "MOV" etc op codes. Nice :-)

Thanks :-)


I'd avoid a name like ProjectPSX to be more distinct from the 100 other PSX emu projects.

PSX.NET or PSX# would be good.


Ey, those are real cool names. The name itself was chosen because i already had a ProjectDMG (Gameboy emulator)... I may go with PSX.net when net core 3.0 goes out...

They may sound cool but they aren't original lol. Open source C# projects like Paint.NET often take on that convention.

This might sound weird, but make sure .NET is capitalized otherwise people might mistake your project for a website.


Very cool work, good job!



Applications are open for YC Winter 2020

Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact

Search: