4 ms·
> Original LambdaMOO had a global interpreter lock and only one user was mutating the world at a time, in their given tick slice. Not gonna scale. I’ve previou
by derefr 3y ago
> Original LambdaMOO had a global interpreter lock and only one user was mutating the world at a time, in their given tick slice. Not gonna scale.
I’ve previously seen (abandoned) attempts to convert the Erlang runtime directly into a MOO runtime. Each command being a concurrent actor-process would make a lot of sense to me. (Though a semantics where all commands are MVCC against the at-inception world-state and will rollback or retry on conflict, kind of moots having any kind the advantage of a runtime based on message-passing, since you’re not going to be passing messages between separate concurrent “sessions.”)
- cmrdporcupine 3y agoYeah the whole actor thing is kind of orthogonal to the question of how you handle the shared world state (which is what makes a MOO a MOO.) There are parts of my runtime that look Erlang-ish, Actor-ish, because that's a concurrency pattern that Rust does well with, in general. In the end a MOO is just a database with fancy stored procedures. And the original code is not a particular... good one. Handling serialization conflicts is what databases have to do as one of their core abilities, and LambdaMOO just tries to punt on the whole question.
- smirya0 3y agohow did you come to pick Rust (just curious, vs Elixir, Java with Loom, etc)?
- cmrdporcupine 3y agoThese days Rust is both my day job language (I work on systems for autonomy in tractors) and my hobby language. It's my preferred tool. I have attempted similar systems in C++ before, and got fairly far along at times. But Rust really solves a lot of problems, I tear my hear out a lot less. This is a "systems" type of application; this is a database and compiler and a virtual machine and language runtime. I'm not super concerned with optimization at this point, but I will likely have to be at some point. Being closer to the metal is key for me on a project like this. I'm likely to e.g. rip out RocksDB and replace it with my own database engine at some point (something I work on here and there as another side project, unpublished still.) I will also likely need a better garbage collection solution. With Rust I have options. The community of libraries available for Rust is good. The right tools are there. And the language is expressive enough to make it work well. That said, I have mulled writing the "host" portion -- the layer that manages network sockets and web endpoints, in Elixir/Erlang: The 'daemon' portion of the system sits behind an RPC layer built around ZeroMQ -- so that layer can be restarted/upgraded without losing active network connections. Or can sit on a separate machine for scalability reasons, etc. So it's entirely possible to split what I'm calling the "host" (network facing layer, what we used to call a "frontend" when I worked at Google, but most people think of that term as referring to a UI layer) from the backend "daemon." Anyways for me this project ties together a bunch of interests in systems programming I've had for years. It becomes a kind of synthesis project.
- bcrosby95 3y ago> Each command being a concurrent actor-process would make a lot of sense to me. I've tossed around doing MUDs in Elixir before. I'm not familiar with MOO though. State has to be owned by a process - who owns the state of the world? Process per "thing" is too fine grained - you're going to have race conditions and deadlocks. Process per "room" can work, but it's still not easy. There are still some cross-room coordinating actions you need to tighten up, and this makes general scripting very hard. Keep in mind, when data is passed "between" processes it is deep copied. This can be a big problem depending upon your process model and how many things your players can carry around. As an experiment I made my own software transactional memory. It worked, assuming no network partitions, but designing and testing it to work in an environment with those was beyond me.
- intelVISA 3y ago> As an experiment I made my own software transactional memory. It worked, assuming no network partitions, but designing and testing it to work in an environment with those was beyond me. BFT STM is a noble goal and, tbh, building performant fault tolerant atomic transaction systems outside of C++ must be quite challenging with all the abstractions atop it
- derefr 3y ago> State has to be owned by a process - who owns the state of the world? ... Keep in mind, when data is passed "between" processes it is deep copied. The Erlang runtime has other stateful things besides processes, and manipulating data within these and/or getting a handle on data in these places doesn't necessarily involve copying. • ETS tables, which hold state privately to themselves (with copying in/out) in a similar way to how processes do. If you manipulate data inside ETS tables by "sending compute to data" (think Redis INCR) rather than by "sending data to compute", then no copying happens. • "Globals" in the form of data compiled into read-only versioned modules loaded into the modules table and available to be referenced from any/all active processes, only copied if the module gets unloaded before the process dies. (Originally this was just a design pattern — https://github.com/discord/fastglobal https://github.com/discord/fastglobal — but it eventually became its own runtime feature in Erlang 22, https://www.erlang.org/doc/man/persistent_term https://www.erlang.org/doc/man/persistent_term). • Large binaries (anything over 64 bytes) aren't allocated in an actor's memory arena, but instead are allocated in a special global-per-node binaries heap, and then ref-counted, where each actor-process holds one reference to each large-binary it's using, and then each read-only slice of that binary, in turn holds a reference to the per-actor reference handle for the binary. One clever technique for sharing a large "database" of data between many actors, is to store the data encoded in a large binary in an encoding that is efficient to partwise-decode; and have the "lookup" operation just parse+decode the appropriate data out of the binary. (This is how erlang:module_info/2 used to work — the global modules-table itself holding a set of references to the loaded modules' binaries, which module_info would then parse metadata out of on demand.)