1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
// (c) 2018 Tuan Tran. Licensed under MIT License.

/*!
**NOTICE: THE LIBRARY IS STILL EXPERIMENTAL AND VERY BUGGY. API IS INCOMPLETE AND SUBJECTED TO CHANGE. PLEASE PROCEED WITH CAUTION**

This crate is an implementation of ObjectSpace - a natural progression on the idea of [TupleSpace](https://en.wikipedia.org/wiki/Tuple_space) proposed by Gelernter in 1985. ObjectSpace is a data structure with the capability of holding any structure (e.g: a string, an int, and a complex struct could all lives under one ObjectSpace). It also allows retrieving a struct based on the value of a field.

This crate also provides a fully thread-safe implementation of ObjectSpace, which allows simple concurrent and distributed programming.

# API

An ObjectSpace could perform the following tasks:

- `write` an object to the space. E.g: `space.write(test_struct)`
- `try_read` (non-blocking), `read` (blocking), and `read_all` structs of a type. E.g: `space.try_read::<TestStruct>()`
- `try_take`, `take`, and `take_all` to remove and returns struct of a type. E.g: `space.try_take::<TestStruct>()`

Notice that an ObjectSpace could hold data from any types, which means that an i64, a String, and a complex struct could all live under one space (which leads to the somewhat wordy API for retrieving items).

Additionally, by implementing `ValueLookupObjectSpace` and `RangeLookupObjectSpace`, an ObjectSpace could retrieve item based on the value of a field. Notice that the field must be a "basic" field: the type of the field must be either an int, a string, or a bool.

E.g: Given a TestStruct:

```rust
struct Prop {
        touched: bool
}

struct TestStruct {
    index: i32,
    property: Prop,
}
```

`space.try_take_by_value::<TestStruct>("property.touched", true)` will return a `TestStruct` with the value `true` for `property.touched`. `space.try_take_by_range::<TestStruct>("index", 2..10)` will return a `TestStruct` with the value of `index` between in the range `2..10`

For further information, please read the documentation of `ObjectSpace`, `RangeLookupObjectSpace`, and `ValueLookupObjectSpace`

# TreeObjectSpace

`TreeSpaceObject` is a referenced implementation of `ObjectSpace` trait. It is, in essence, a concurrent HashMap of `TypeId` and corresponding `Entry` for each type. Each `Entry` stores objects by serializing & flattening their structure, then put the values of basic fields in a `BTreeMap` for efficient lookup. `TreeSpaceObject` is thread-safe, which allows it to be used in concurrent and distributed settings.

# Example

Here is a program to calculate all primes up to a limit using ObjectSpace
 
```rust
extern crate object_space;
extern crate serde;
#[macro_use]
extern crate serde_derive;

use std::thread;
use std::env;
use std::sync::Arc;

use object_space::{ObjectSpace, ValueLookupObjectSpace, RangeLookupObjectSpace, TreeObjectSpace};

fn main() {
    let mut args = env::args();
    let upper_lim = 1000000;
    let thread_count = 4;

    // setup. add 2 & 3 just because we can
    let mut n = 4;
    let space = Arc::new(TreeObjectSpace::new());
    space.write::<i64>(2);
    space.write::<i64>(3);

    // create 4 worker threads
    for _ in 0..thread_count {
        let space_clone = space.clone();
        thread::spawn(move || {
            check_numbers(space_clone);
        });
    }

    // continue until we hit limit
    while n < upper_lim {
        let max = if n * n < upper_lim { n * n } else { upper_lim };

        for i in 0..thread_count {
            // divide work evenly between threads
            let start =
                n + (((max - n) as f64) / (thread_count as f64) * (i as f64)).round() as i64;
            let end =
                n + (((max - n) as f64) / (thread_count as f64) * ((i + 1) as f64)).round() as i64;

            let clone = space.clone();
            clone.write(Task {
                finished: false,
                start: start,
                end: end,
            });
        }

        // "joining" threads
        for _ in 0..thread_count {
            let clone = space.clone();
            clone.take_by_value::<Task>("finished", &true);
        }
        n = max;
    }
}

fn check_numbers(space: Arc<TreeObjectSpace>) {
    loop {
        let task = space.take_by_value::<Task>("finished", &false);
        let max = task.end;
        let min = task.start;
        let primes: Vec<i64> = space.read_all::<i64>().filter(|i| i * i < max).collect();
        for i in min..max {
            if primes.iter().all(|prime| i % prime != 0) {
                space.write(i);
            }
        }
        space.write(Task {
            finished: true,
            start: min,
            end: max,
        });
    }
}

#[derive(Serialize, Deserialize)]
struct Task {
    finished: bool,
    start: i64,
    end: i64,
}
```
*/

#![feature(collections_range)]
extern crate chashmap;
extern crate indexmap;
extern crate ordered_float;
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;

pub use self::object_space::*;
mod entry;
mod helpers;
mod object_space;