use std::sync::atomic::{AtomicUsize, Ordering};
use std::mem;
use std::marker::PhantomData;
use std::io;
use std::fmt;
use std::cell::RefCell;
use fnv::FnvHashMap;
use crate::idset::IdSet;
use crate::handler::SharedDirtiedHandler;
thread_local! {
static THREAD_SYSTEM: RefCell<Option<ThreadRuntime>> = RefCell::new(None);
}
#[cfg(debug_assertions)]
type NodeLabel = String;
#[cfg(not(debug_assertions))]
type NodeLabel = ();
struct NodeInfo {
label: NodeLabel,
used_by: IdSet,
uses: IdSet,
dirty: bool,
on_dirty: Option<SharedDirtiedHandler>,
}
impl NodeInfo {
fn new() -> Self {
NodeInfo {
label: NodeLabel::default(),
used_by: IdSet::new(),
uses: IdSet::new(),
dirty: true, on_dirty: None,
}
}
fn propagate_dirty(
&mut self,
id: usize,
cause: usize,
keep_cause_dirty: bool,
to_dirty: &mut Vec<usize>,
) {
let mut should_propagate = false;
if id == cause {
if keep_cause_dirty {
should_propagate = true;
} else {
self.dirty = false;
}
} else if !self.dirty {
should_propagate = true;
self.dirty = true;
if let Some(handler) = self.on_dirty.as_mut() {
handler.on_dirtied(id);
}
}
if should_propagate {
to_dirty.extend(self.used_by.iter());
}
}
}
#[cfg(debug_assertions)]
fn fmt_label(
var: Option<&NodeInfo>,
id: usize,
f: &mut fmt::Formatter,
) -> fmt::Result {
match var {
Some(v) if !v.label.is_empty() => fmt::Debug::fmt(&v.label, f),
_ => fmt::Debug::fmt(&id, f),
}
}
#[cfg(not(debug_assertions))]
fn fmt_label(
var: Option<&NodeInfo>,
id: usize,
f: &mut fmt::Formatter,
) -> fmt::Result {
let _ = var.map(|info| info.label); fmt::Debug::fmt(&id, f)
}
#[derive(Default)]
pub struct ThreadRuntime {
id_buf: Vec<usize>,
computing_stack: Vec<(usize, bool)>,
nodes: FnvHashMap<usize, NodeInfo>,
}
impl ThreadRuntime {
fn with<R>(func: impl FnOnce(&mut ThreadRuntime) -> R) -> R {
THREAD_SYSTEM.with(|sys| func(sys.borrow_mut()
.get_or_insert_with(ThreadRuntime::default)))
}
pub fn replace_current(self) -> ThreadRuntime {
ThreadRuntime::with(|sys| mem::replace(sys, self))
}
fn set_dependent_impl(&mut self, used_by: usize, uses: usize) {
if uses == used_by {
return;
}
self.nodes.entry(used_by)
.or_insert_with(NodeInfo::new)
.uses
.insert(uses);
self.nodes.entry(uses)
.or_insert_with(NodeInfo::new)
.used_by
.insert(used_by);
}
pub fn write_graphviz(mut to: impl io::Write) -> io::Result<()> {
ThreadRuntime::with(|sys| write!(to, "{:?}", sys))
}
}
impl fmt::Debug for ThreadRuntime {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("digraph {\n")?;
for (&id, var) in self.nodes.iter() {
write!(f, "{} [label=", id)?;
fmt_label(Some(&var), id, f)?;
writeln!(f, ", color={}];", match var.dirty {
true => "red",
false => "black",
})?;
}
for (&used_by_id, used_by_var) in self.nodes.iter() {
for uses_id in used_by_var.uses.iter() {
writeln!(f, "{} -> {};", uses_id, used_by_id)?;
}
}
f.write_str("}\n")
}
}
pub struct ComputationHandle(usize);
impl ComputationHandle {
pub fn set_reactive_area(&self, should_react: bool) {
ThreadRuntime::with(|sys| {
sys.computing_stack
.get_mut(self.0)
.expect("computation stack corrupted")
.1 = should_react;
})
}
pub fn is_reactive_area(&self) -> bool {
ThreadRuntime::with(|sys| {
sys.computing_stack
.get_mut(self.0)
.expect("computation stack corrupted")
.1
})
}
}
impl Drop for ComputationHandle {
fn drop(&mut self) {
ThreadRuntime::with(|sys| {
assert_eq!(
sys.computing_stack.len(),
self.0 + 1,
"computation stack corrupted",
);
sys.computing_stack.pop();
})
}
}
static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
#[derive(PartialEq, Eq, Hash)]
#[repr(transparent)]
pub struct Node {
pub id: usize,
phantom: PhantomData<*mut ()>,
}
impl Node {
pub fn next() -> Self {
Node {
id: NEXT_ID.fetch_add(1, Ordering::Relaxed),
phantom: PhantomData,
}
}
pub fn computation<T>(&self, func: impl FnOnce(&ComputationHandle) -> T) -> T {
let handle = ThreadRuntime::with(|sys| {
if let Some(var) = sys.nodes.get_mut(&self.id) {
sys.id_buf.clear();
sys.id_buf.extend(var.uses.drain());
for uses in sys.id_buf.drain(..) {
if let Some(uses) = sys.nodes.get_mut(&uses) {
uses.used_by.remove(self.id);
}
}
}
let handle = ComputationHandle(sys.computing_stack.len());
sys.computing_stack.push((self.id, true));
handle
});
func(&handle)
}
pub fn add_dependency(&self, on: &Node) {
ThreadRuntime::with(|sys| sys.set_dependent_impl(self.id, on.id));
}
pub fn depends_on(&self, on: &Node) -> bool {
ThreadRuntime::with(|sys| sys.nodes.get(&self.id)
.map(|var| var.uses.contains(on.id))
.unwrap_or(false))
}
pub fn num_dependencies(&self) -> usize {
ThreadRuntime::with(|sys| sys.nodes.get(&self.id)
.map(|var| var.uses.len())
.unwrap_or(0))
}
pub fn num_dependents(&self) -> usize {
ThreadRuntime::with(|sys| sys.nodes.get(&self.id)
.map(|var| var.used_by.len())
.unwrap_or(0))
}
pub fn is_dirty(&self) -> bool {
ThreadRuntime::with(|sys| sys.nodes.get(&self.id)
.map(|var| var.dirty)
.unwrap_or(true))
}
pub fn on_write(&self, keep_dirty: bool) {
ThreadRuntime::with(|sys| {
sys.id_buf.clear();
sys.id_buf.push(self.id);
if !keep_dirty {
sys.nodes.entry(self.id).or_insert_with(NodeInfo::new);
}
while let Some(dep_id) = sys.id_buf.pop() {
if let Some(var) = sys.nodes.get_mut(&dep_id) {
var.propagate_dirty(
dep_id, self.id, keep_dirty, &mut sys.id_buf, );
}
}
});
}
pub fn on_read(&self) {
ThreadRuntime::with(|sys| {
if let Some(&(used_by, true)) = sys.computing_stack.last() {
sys.set_dependent_impl(used_by, self.id);
}
});
}
pub fn send_dirtied_signal_to(&self, handler: SharedDirtiedHandler) {
ThreadRuntime::with(|sys| {
sys.nodes.entry(self.id)
.or_insert_with(NodeInfo::new)
.on_dirty = Some(handler);
});
}
#[cfg(debug_assertions)]
pub fn set_label(&self, text: impl fmt::Display) {
use fmt::Write;
ThreadRuntime::with(|sys| {
let label = &mut sys.nodes
.entry(self.id)
.or_insert_with(NodeInfo::new)
.label;
write!(label, "{}", text).unwrap();
})
}
#[cfg(not(debug_assertions))]
pub fn set_label(&self, _text: impl fmt::Display) { }
pub fn from_id(id: usize) -> mem::ManuallyDrop<Node> {
mem::ManuallyDrop::new(Node {
id,
phantom: PhantomData,
})
}
pub fn clone_id(&self) -> mem::ManuallyDrop<Node> {
Node::from_id(self.id)
}
}
#[cfg(debug_assertions)]
impl fmt::Debug for Node {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
ThreadRuntime::with(|sys| fmt_label(
sys.nodes.get(&self.id),
self.id,
f,
))
}
}
#[cfg(not(debug_assertions))]
impl fmt::Debug for Node {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt_label(None, self.id, f)
}
}
impl Drop for Node {
fn drop(&mut self) {
let id = self.id;
ThreadRuntime::with(|sys| {
if let Some(var) = sys.nodes.remove(&id) {
for uses in var.uses.iter() {
if let Some(uses) = sys.nodes.get_mut(&uses) {
uses.used_by.remove(id);
}
}
for used_by in var.used_by.iter() {
if let Some(used_by) = sys.nodes.get_mut(&used_by) {
used_by.uses.remove(id);
}
}
}
});
}
}
#[no_mangle]
extern "C" fn reax_node_create() -> usize {
mem::ManuallyDrop::new(Node::next()).id
}
#[no_mangle]
extern "C" fn reax_node_destroy(node: usize) {
mem::ManuallyDrop::into_inner(Node::from_id(node));
}
#[no_mangle]
extern "C" fn reax_node_is_dirty(node: usize) -> bool {
Node::from_id(node).is_dirty()
}
#[no_mangle]
extern "C" fn reax_node_num_dependents(node: usize) -> usize {
Node::from_id(node).num_dependents()
}
#[no_mangle]
extern "C" fn reax_node_num_dependencies(node: usize) -> usize {
Node::from_id(node).num_dependencies()
}
#[no_mangle]
extern "C" fn reax_node_add_dependency(node: usize, on: usize) {
Node::from_id(node).add_dependency(&*Node::from_id(on))
}
#[no_mangle]
extern "C" fn reax_node_on_write(node: usize, keep_dirty: bool) {
Node::from_id(node).on_write(keep_dirty)
}
#[no_mangle]
extern "C" fn reax_node_on_read(node: usize) {
Node::from_id(node).on_read()
}
#[no_mangle]
unsafe extern "C" fn reax_write_graphviz(ptr: *mut u8, capacity: usize) -> usize {
let buf = std::slice::from_raw_parts_mut(ptr, capacity);
let mut writer = io::Cursor::new(buf);
match ThreadRuntime::write_graphviz(&mut writer) {
Ok(_) => writer.position() as usize,
Err(_) => 0,
}
}
#[no_mangle]
extern "C" fn reax_computation_push(node: usize) {
ThreadRuntime::with(|rt| {
rt.computing_stack.push((node, true));
})
}
#[no_mangle]
extern "C" fn reax_computation_pop() {
ThreadRuntime::with(|rt| {
rt.computing_stack.pop();
})
}
#[no_mangle]
extern "C" fn reax_computation_set_reactive(reactive: bool) {
ThreadRuntime::with(|rt| {
if let Some((_, ref mut r)) = rt.computing_stack.last_mut() {
*r = reactive;
}
})
}
#[no_mangle]
extern "C" fn reax_computation_is_reactive() -> bool {
ThreadRuntime::with(|rt| {
rt.computing_stack.last().map(|&(_, r)| r).unwrap_or(false)
})
}