Giter Site home page Giter Site logo

andross's Introduction

andross

An [experimental] Project Slippi client/relay, written in Rust!

TODO

  • Flush console data for a session to a .slp file on disk
  • Clean up buffers in-between sessions and reset consumer threads
  • Devise some nice way to handle client/console disconnect
  • Go faster, optimize timing, etc.
  • Expose some interface for sending real-time, "higher-level" messages to clients?

Implementation Notes

I haven't written a lot of Rust, so I feel compelled to have some very pedantic rambling about how things work right now (mostly for my own reference, but I guess it might be useful for you too, especially if you're also new to Rust)! If anything in these notes seems incorrect, please correct me with a pull request! Also, as of right now, this program is written for Linux, and any compatibility with other platforms is unknown.

There are two main things that this program has to do very, very well. Maintaining guarantees about timely delivery of messages is a priority:

  • We always need to ingest data from a console as soon as it is available on a socket. Assuming we're connected over Ethernet, we have reasonable guarantees that this will happen every frame. At ~60fps, this means new data should arrive every ~16ms.

  • We always need to emit [raw] data to consumers as soon as it has been ingested from a console. If we cannot instantaneously deliver data for a particular frame, we must strive to deliver data before we receive the next frame's data. Under ideal network conditions, this means that we have a ~16ms window where we can deliver data without necessarily introducing an extra frame of lag downstream during mirroring.

Right now, this program dedicates separate threads to handling the work associated with remote clients: one thread for ingesting data from a console (the console thread), and N unique threads for emitting data to clients (consumer threads).

I/O Behaviour

I/O needs to be serialized in the following manner:

  1. Receive data from the console for frame X
  2. Emit data for frame X to all clients
  3. Receive data from the console for frame X+1
  4. Emit data for frame X+1 to all clients
  5. ...

All I/O needs to be non-blocking. Particularly, for the console thread, we need to be able to go off-CPU in-between messages. This allows us to put some consumer threads on-CPU in the time between frames on console.

The console thread uses the mio crate to deal with receive messages without blocking. On Linux, mio::Poll uses the epoll() API, which allows the kernel to register file descriptors and watch them for "events" while putting the current process to sleep (basically, when the kernel doesn't see a relevant "event" on a file descriptor, it puts us off-CPU by setting us to TASK_INTERRUPTIBLE; see fs/eventpoll.c).

Currently, consumer threads block when calling send(). I'm actually not sure how non-blocking writes to sockets work right now, or how they'd be useful in this situation.

Safe Access to Shared Resources

TODO: How is Arc/Mutex implemented in Rust? What are the low-level details?

In this model, all threads need to share access to some memory where console data is stored. On top of this, we also need some way to serialize the order in which threads are put on-CPU. "Fortunately" for us, Rust's compiler will literally refuse to spit out code if we haven't convinced it that everything important has been serialized, and that accesses to shared memory are all accounted for.

Currently, because messages (and the number of messages) received from console might be variable-length, we use a Vec<Vec<u8>> to have some dynamic list of messages on the heap. In order to have guarantees that accesses to the buffer are race-free, we need to wrap this up with two of Rust's synchronization primitives: an Arc, and a Mutex.

In Rust, std::sync::Arc seems to be the idiomatic way to keep track of all references to shared memory. Arc is basically a pointer to some memory which, when cloned, increases a reference count. This gives us ("us" meaning, the compiler) a way to reason about ownership to the underlying data during compile-time.

Additionally, in order to have mutable shared references to some memory, we also need to wrap the buffer up with std::sync::Mutex. In order to use the buffer, a thread must first acquire the lock. Due to the nice properties of Rust's memory model (i.e. "ownership," "lifetime," things being implicitly immutable, etc), this lock should be automatically freed when the compiler notices the reference moving out-of-scope. On the other hand, if there is a situation where another thread is put on-CPU and attempts to acquire the lock, it will necessarily block until the lock is freed up.

Control over Thread Scheduling

TODO: How are channels implemented in Rust?

In order to reason about things, it's nice to have some way of "sending a message" from one thread to another: particularly, we need the console thread to send some message to all consumer threads, which causes them to move on-CPU.

The bus crate seems to offer an alternative to Rust's std::sync::mpsc::channel which implements the notion of "broadcasting" some message to all receiving ends of a channel. My current understanding of this situation is:

  • Since we need the console thread to broadcast (which requires a mutable reference), and we need the main thread to create and clone new "receiving ends" on the bus (which also requires a mutable reference), we need to create Arc<Mutex<Bus>> so that changes to the Bus are always serialized.

  • Whenever the console thread receives data from a frame, we emit a message to all consumer threads

  • Consumer threads start by calling .recv(), which causes them to go off-CPU until a message is consumed.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.