diff --git a/src/lib.rs b/src/lib.rs index 357f234aa26047196d17a6c8e5f2a3727e53f6b3..1f3e76cc3424f898e5c1dad63dca15e481f43457 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,7 @@ mod macros; // mod common; mod protocol; pub mod runtime; +pub mod runtime2; mod collections; pub use protocol::ProtocolDescription; \ No newline at end of file diff --git a/src/runtime2/communication.rs b/src/runtime2/communication.rs new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/runtime2/component.rs b/src/runtime2/component.rs new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/runtime2/mod.rs b/src/runtime2/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..29342214db784707f350f2fc57a43d559c71cf80 --- /dev/null +++ b/src/runtime2/mod.rs @@ -0,0 +1,4 @@ + +mod runtime; +mod component; +mod communication; \ No newline at end of file diff --git a/src/runtime2/runtime.rs b/src/runtime2/runtime.rs new file mode 100644 index 0000000000000000000000000000000000000000..37e2cfea71e904e20e3166ec56f45ddb5b157473 --- /dev/null +++ b/src/runtime2/runtime.rs @@ -0,0 +1,119 @@ +use std::sync::Arc; +use std::sync::atomic::AtomicU32; + +use crate::protocol::*; + +// ----------------------------------------------------------------------------- +// Component +// ----------------------------------------------------------------------------- + +/// Key to a component. Type system somewhat ensures that there can only be one +/// of these. Only with a key one may retrieve privately-accessible memory for +/// a component. Practically just a generational index, like `CompId` is. +#[derive(Copy, Clone)] +pub(crate) struct CompKey(CompId); + +/// Generational ID of a component +#[derive(Copy, Clone)] +pub(crate) struct CompId { + pub index: u32, + pub generation: u32, +} + +impl PartialEq for CompId { + fn eq(&self, other: &Self) -> bool { + return self.index.eq(&other.index); + } +} +impl Eq for CompId {} + +/// In-runtime storage of a component +pub(crate) struct RtComp { + +} + +// ----------------------------------------------------------------------------- +// Runtime +// ----------------------------------------------------------------------------- + +type RuntimeHandle = Arc; + +/// Memory that is maintained by "the runtime". In practice it is maintained by +/// multiple schedulers, and this serves as the common interface to that memory. +pub struct Runtime { + active_elements: AtomicU32, // active components and APIs (i.e. component creators) +} + +impl Runtime { + pub fn new(num_threads: u32, protocol_description: ProtocolDescription) -> Runtime { + assert!(num_threads > 0, "need a thread to perform work"); + return Runtime{ + active_elements: AtomicU32::new(0), + }; + } +} + +// ----------------------------------------------------------------------------- +// Runtime containers +// ----------------------------------------------------------------------------- + +/// Component storage. Note that it shouldn't be polymorphic, but making it so +/// allows us to test it. +// Requirements: +// 1. Performance "fastness" in order of most important: +// 1. Access (should be just index retrieval) +// 2. Creation (because we want to execute code as fast as possible) +// 3. Destruction (because create-and-run is more important than quick dying) +// 2. Somewhat safe, with most performance spent in the incorrect case +// 3. Thread-safe. Everyone and their dog will be creating and indexing into +// the components concurrently. +// 4. Assume low contention. +// +// Some trade-offs: +// We could perhaps make component IDs just a pointer to that component. With +// an atomic counter managed by the runtime containing the number of owners +// (always starts at 1). However, this feels like too early to do something like +// that, especially because I would like to do direct messaging. Even though +// sending two u32s is the same as sending a pointer, it feels wrong for now. +// +// So instead we'll have some kind of concurrent store where we can index into. +// This means that it might have to resize. Resizing implies that everyone must +// wait until it is resized. +// +// Furthermore, it would be nice to reuse slots. That is to say: if we create a +// bunch of components and then destroy a couple of them, then the storage we +// reserved for them should be reusable. +// +// We'll go the somewhat simple route for now: +// 1. Each component will get allocated individually (and we'll define exactly +// what we mean by this sometime later, when we start with the bytecode). This +// way the components are pointer-stable for their lifetime. +// 2. We need to have some array that contains these pointers. We index into +// this array with our IDs. +// 3. When we destroy components we call the destructor on the allocated memory +// and add the index to some kind of freelist. Because only one thread can ever +// create and/or destroy a component we have an imaginary lock on that +// particular component's index. The freelist acts like a concurrent stack +// where we can push/pop. If we ensure that the freelist is the same size as +// the ID array then we can never run out of size. +// 4. At some point the array ID might be full and have to be resized. If we +// ensure that there is only one thread which can ever fill up the array (this +// means we *always* have one slot free, such that we can do a CAS) then we can +// do a pointer-swap on the base pointer of all storage. This takes care of +// resizing due to creation. +// +// However, with a freelist accessed at the same time, we must make sure that +// we do the copying of the old freelist and the old ID array correctly. While +// we're creating the new array we might still be destroying components. So +// one component calls a destructor (not too bad) and then pushes the resulting +// ID onto the freelist stack (which is bad). We can either somehow forbid +// destroying during resizing (which feels ridiculous) or try to be smart. Note +// that destruction might cause later creations as well! +// +// Since components might have to read a base pointer anyway to arrive at a +// freelist entry or component pointer, we could set it to null and let the +// others spinlock (or take a mutex?). So then the resizer will notice the +// +struct CompStore { + +} \ No newline at end of file