Files @ ccd08a8d8365
Branch filter:

Location: CSY/reowolf/src/runtime2/runtime.rs

ccd08a8d8365 5.1 KiB application/rls-services+xml Show Annotation Show as Raw Download as Raw
mh
Preparing component store
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<Runtime>;

/// 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 {

}