use crate::prelude::*;
use holochain_tracing::Span;
pub struct GhostParentWrapper<
UserData,
RequestToParent: 'static,
RequestToParentResponse: 'static,
RequestToChild: 'static,
RequestToChildResponse: 'static,
Error: 'static + std::fmt::Debug,
Actor: GhostActor<
RequestToParent,
RequestToParentResponse,
RequestToChild,
RequestToChildResponse,
Error,
>,
> {
actor: Actor,
endpoint: GhostContextEndpoint<
UserData,
RequestToChild,
RequestToChildResponse,
RequestToParent,
RequestToParentResponse,
Error,
>,
}
impl<
UserData,
RequestToParent: 'static,
RequestToParentResponse: 'static,
RequestToChild: 'static,
RequestToChildResponse: 'static,
Error: 'static + std::fmt::Debug,
Actor: GhostActor<
RequestToParent,
RequestToParentResponse,
RequestToChild,
RequestToChildResponse,
Error,
>,
>
GhostParentWrapper<
UserData,
RequestToParent,
RequestToParentResponse,
RequestToChild,
RequestToChildResponse,
Error,
Actor,
>
{
pub fn new(mut actor: Actor, request_id_prefix: &str) -> Self {
let endpoint = actor
.take_parent_endpoint()
.expect("exists")
.as_context_endpoint_builder()
.request_id_prefix(request_id_prefix)
.build();
Self { actor, endpoint }
}
}
impl<
UserData,
RequestToParent: 'static,
RequestToParentResponse: 'static,
RequestToChild: 'static,
RequestToChildResponse: 'static,
Error: 'static + std::fmt::Debug,
Actor: GhostActor<
RequestToParent,
RequestToParentResponse,
RequestToChild,
RequestToChildResponse,
Error,
>,
>
GhostCanTrack<
UserData,
RequestToChild,
RequestToChildResponse,
RequestToParent,
RequestToParentResponse,
Error,
>
for GhostParentWrapper<
UserData,
RequestToParent,
RequestToParentResponse,
RequestToChild,
RequestToChildResponse,
Error,
Actor,
>
{
fn publish(&mut self, span: Span, payload: RequestToChild) -> GhostResult<()> {
self.endpoint.publish(span, payload)
}
fn request(
&mut self,
span: Span,
payload: RequestToChild,
cb: GhostCallback<UserData, RequestToChildResponse, Error>,
) -> GhostResult<()> {
self.endpoint.request(span, payload, cb)
}
fn request_options(
&mut self,
span: Span,
payload: RequestToChild,
cb: GhostCallback<UserData, RequestToChildResponse, Error>,
options: GhostTrackRequestOptions,
) -> GhostResult<()> {
self.endpoint.request_options(span, payload, cb, options)
}
fn drain_messages(
&mut self,
) -> Vec<GhostMessage<RequestToParent, RequestToChild, RequestToParentResponse, Error>> {
self.endpoint.drain_messages()
}
fn process(&mut self, user_data: &mut UserData) -> GhostResult<WorkWasDone> {
let work_was_done = self.actor.process()?;
let _endpoint_did_work = self.endpoint.process(user_data)?;
Ok(work_was_done)
}
}
impl<
UserData,
RequestToParent: 'static,
RequestToParentResponse: 'static,
RequestToChild: 'static,
RequestToChildResponse: 'static,
Error: 'static + std::fmt::Debug,
Actor: GhostActor<
RequestToParent,
RequestToParentResponse,
RequestToChild,
RequestToChildResponse,
Error,
>,
> std::convert::AsRef<Actor>
for GhostParentWrapper<
UserData,
RequestToParent,
RequestToParentResponse,
RequestToChild,
RequestToChildResponse,
Error,
Actor,
>
{
fn as_ref(&self) -> &Actor {
&self.actor
}
}
impl<
UserData,
RequestToParent: 'static,
RequestToParentResponse: 'static,
RequestToChild: 'static,
RequestToChildResponse: 'static,
Error: 'static + std::fmt::Debug,
Actor: GhostActor<
RequestToParent,
RequestToParentResponse,
RequestToChild,
RequestToChildResponse,
Error,
>,
> std::convert::AsMut<Actor>
for GhostParentWrapper<
UserData,
RequestToParent,
RequestToParentResponse,
RequestToChild,
RequestToChildResponse,
Error,
Actor,
>
{
fn as_mut(&mut self) -> &mut Actor {
&mut self.actor
}
}
pub trait GhostActor<
RequestToParent: 'static,
RequestToParentResponse: 'static,
RequestToChild: 'static,
RequestToChildResponse: 'static,
Error: 'static + std::fmt::Debug,
>
{
fn take_parent_endpoint(
&mut self,
) -> Option<
GhostEndpoint<
RequestToChild,
RequestToChildResponse,
RequestToParent,
RequestToParentResponse,
Error,
>,
>;
fn process(&mut self) -> GhostResult<WorkWasDone> {
self.process_concrete()
}
fn process_concrete(&mut self) -> GhostResult<WorkWasDone> {
Ok(false.into())
}
}
pub struct GhostParentWrapperDyn<
UserData,
RequestToParent: 'static,
RequestToParentResponse: 'static,
RequestToChild: 'static,
RequestToChildResponse: 'static,
Error: 'static + std::fmt::Debug,
> {
actor: Box<
dyn GhostActor<
RequestToParent,
RequestToParentResponse,
RequestToChild,
RequestToChildResponse,
Error,
>,
>,
endpoint: GhostContextEndpoint<
UserData,
RequestToChild,
RequestToChildResponse,
RequestToParent,
RequestToParentResponse,
Error,
>,
}
impl<
UserData,
RequestToParent: 'static,
RequestToParentResponse: 'static,
RequestToChild: 'static,
RequestToChildResponse: 'static,
Error: 'static + std::fmt::Debug,
>
GhostParentWrapperDyn<
UserData,
RequestToParent,
RequestToParentResponse,
RequestToChild,
RequestToChildResponse,
Error,
>
{
pub fn new(
mut actor: Box<
dyn GhostActor<
RequestToParent,
RequestToParentResponse,
RequestToChild,
RequestToChildResponse,
Error,
>,
>,
request_id_prefix: &str,
) -> Self {
let endpoint: GhostContextEndpoint<UserData, _, _, _, _, _> = actor
.take_parent_endpoint()
.expect("exists")
.as_context_endpoint_builder()
.request_id_prefix(request_id_prefix)
.build();
Self { actor, endpoint }
}
}
impl<
UserData,
RequestToParent: 'static,
RequestToParentResponse: 'static,
RequestToChild: 'static,
RequestToChildResponse: 'static,
Error: 'static + std::fmt::Debug,
>
GhostCanTrack<
UserData,
RequestToChild,
RequestToChildResponse,
RequestToParent,
RequestToParentResponse,
Error,
>
for GhostParentWrapperDyn<
UserData,
RequestToParent,
RequestToParentResponse,
RequestToChild,
RequestToChildResponse,
Error,
>
{
fn publish(&mut self, span: Span, payload: RequestToChild) -> GhostResult<()> {
self.endpoint.publish(span, payload)
}
fn request(
&mut self,
span: Span,
payload: RequestToChild,
cb: GhostCallback<UserData, RequestToChildResponse, Error>,
) -> GhostResult<()> {
self.endpoint.request(span, payload, cb)
}
fn request_options(
&mut self,
span: Span,
payload: RequestToChild,
cb: GhostCallback<UserData, RequestToChildResponse, Error>,
options: GhostTrackRequestOptions,
) -> GhostResult<()> {
self.endpoint.request_options(span, payload, cb, options)
}
fn drain_messages(
&mut self,
) -> Vec<GhostMessage<RequestToParent, RequestToChild, RequestToParentResponse, Error>> {
self.endpoint.drain_messages()
}
fn process(&mut self, user_data: &mut UserData) -> GhostResult<WorkWasDone> {
let mut work_was_done = self.actor.process()?;
work_was_done = work_was_done.or(self.endpoint.process(user_data)?);
Ok(work_was_done)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{ghost_channel::create_ghost_channel, ghost_tracker::GhostCallbackData};
use detach::prelude::*;
use holochain_tracing::test_span;
type TestError = String;
#[derive(Debug)]
struct TestMsgOut(String);
#[derive(Debug, PartialEq)]
struct TestMsgOutResponse(String);
#[derive(Debug)]
struct TestMsgIn(String);
#[derive(Debug, PartialEq, Clone)]
struct TestMsgInResponse(String);
struct TestActor {
endpoint_for_parent: Option<
GhostEndpoint<TestMsgIn, TestMsgInResponse, TestMsgOut, TestMsgOutResponse, TestError>,
>,
endpoint_as_child: Detach<
GhostContextEndpoint<
TestActor,
TestMsgOut,
TestMsgOutResponse,
TestMsgIn,
TestMsgInResponse,
TestError,
>,
>,
internal_state: Vec<String>,
}
impl TestActor {
pub fn new() -> Self {
let (endpoint_parent, endpoint_self) = create_ghost_channel();
Self {
endpoint_for_parent: Some(endpoint_parent),
endpoint_as_child: Detach::new(
endpoint_self
.as_context_endpoint_builder()
.request_id_prefix("child")
.build(),
),
internal_state: Vec::new(),
}
}
}
impl GhostActor<TestMsgOut, TestMsgOutResponse, TestMsgIn, TestMsgInResponse, TestError>
for TestActor
{
fn take_parent_endpoint(
&mut self,
) -> Option<
GhostEndpoint<TestMsgIn, TestMsgInResponse, TestMsgOut, TestMsgOutResponse, TestError>,
> {
std::mem::replace(&mut self.endpoint_for_parent, None)
}
fn process_concrete(&mut self) -> GhostResult<WorkWasDone> {
println!("process_concrete!");
detach_run!(&mut self.endpoint_as_child, |cs| cs.process(self))?;
let mut did_work = false;
for mut msg in self.endpoint_as_child.as_mut().drain_messages() {
println!("process_concrete, got msg");
let payload = match msg.take_message().expect("exists") {
TestMsgIn(payload) => payload,
};
self.internal_state.push(payload.clone());
if msg.is_request() {
msg.respond(Ok(TestMsgInResponse(format!("we got: {}", payload))))?;
};
did_work |= true;
}
Ok(did_work.into())
}
}
struct FakeParent {
state: String,
}
#[test]
fn test_ghost_actor() {
let mut fake_parent = FakeParent {
state: "".to_string(),
};
let mut child_actor = TestActor::new();
let mut parent_endpoint: GhostContextEndpoint<
FakeParent,
TestMsgIn,
TestMsgInResponse,
TestMsgOut,
TestMsgOutResponse,
TestError,
> = child_actor
.take_parent_endpoint()
.unwrap()
.as_context_endpoint_builder()
.request_id_prefix("parent")
.build();
let span = test_span("test_ghost_actor");
parent_endpoint
.publish(span, TestMsgIn("event from parent".into()))
.unwrap();
assert!(child_actor.process().is_ok());
assert_eq!(
"\"event from parent\"",
format!("{:?}", child_actor.internal_state[0])
);
let cb: GhostCallback<FakeParent, TestMsgInResponse, TestError> =
Box::new(|parent, callback_data| {
if let GhostCallbackData::Response(Ok(TestMsgInResponse(payload))) = callback_data {
parent.state = payload;
}
Ok(())
});
parent_endpoint
.request(
test_span("context data"),
TestMsgIn("event from parent".into()),
cb,
)
.unwrap();
assert!(child_actor.process().is_ok());
assert!(parent_endpoint.process(&mut fake_parent).is_ok());
assert_eq!("we got: event from parent", fake_parent.state);
}
#[test]
fn test_ghost_actor_parent_wrapper() {
let mut fake_parent = FakeParent {
state: "".to_string(),
};
let mut wrapped_child: GhostParentWrapper<
FakeParent,
TestMsgOut,
TestMsgOutResponse,
TestMsgIn,
TestMsgInResponse,
TestError,
TestActor,
> = GhostParentWrapper::new(TestActor::new(), "parent");
wrapped_child
.publish(test_span(""), TestMsgIn("event from parent".into()))
.unwrap();
assert!(wrapped_child.process(&mut fake_parent).is_ok());
assert_eq!(
"\"event from parent\"",
format!("{:?}", wrapped_child.as_ref().internal_state[0])
)
}
}