Skip to content

Instantly share code, notes, and snippets.

@Jakintosh
Last active Jul 30, 2021
Embed
What would you like to do?
A test of calling the holochain conductor from C# (Unity) app
using NativeWebSocket;
using SouthPointe.Serialization.MessagePack;
using System;
using UnityEngine;
namespace Client {
public class App : MonoBehaviour {
[System.Serializable]
public class MyInput {
public string first_name;
public string last_name;
public MyInput ( string firstName, string lastName ) {
first_name = firstName;
last_name = lastName;
}
}
[System.Serializable]
public class MyOutput {
public string output;
public override string ToString () => $"MyOutput({name})";
}
// monobehaviour lifecycle
private void Awake () {
InitHolochain();
}
private void Update () {
if ( Input.GetKeyDown( KeyCode.Space ) ) {
SendRequest();
}
conductor.DispatchMessageQueue();
}
private void OnApplicationQuit () {
conductor.CloseConnection();
}
private Holochain.AppConductor conductor;
private void InitHolochain () {
var dnaHash = Holochain.DNAHash.Get();
var agentPubKey = Holochain.AgentPubKey.Get();
conductor = new Holochain.AppConductor(
dnaHash: dnaHash,
agentPubKey: agentPubKey
);
}
private void SendRequest () {
var formatter = new MessagePackFormatter();
var payload = formatter.Serialize(
new MyInput(
firstName: "Jak",
lastName: "Tiano"
)
);
conductor.CallFunction(
zome: "simple",
function: "say_my_name",
payload: payload
);
}
}
}
namespace Holochain {
static class AgentPubKey {
private static string _agentPubKey = "uhCAkt_cNGyYJZIp08b2ZzxoE6EqPndRPb_WwjVkM_mOBcFyq7zCw";
public static byte[] Get () => System.Text.Encoding.UTF8.GetBytes( _agentPubKey );
}
static class DNAHash {
private static string _dnaHash = "uhC0kTMixTG0lNZCF4SZfQMGozf2WfjQht7E06_wy3h29-zPpWxPQ";
public static byte[] Get () => System.Text.Encoding.UTF8.GetBytes( _dnaHash );
}
static class CellID {
public static byte[][] Get ()
=> new byte[2][] { DNAHash.Get(), AgentPubKey.Get() };
public static byte[][] Create ( byte[] dnaHash, byte[] agentPubKey )
=> new byte[2][] { dnaHash, agentPubKey };
}
[Serializable]
public class RustEnum<T> {
public string type;
public T data;
public RustEnum ( string type, T data ) {
this.type = type;
this.data = data;
}
}
[Serializable]
public class WireMessage : RustEnum<byte[]> {
public long id;
public WireMessage (
string type,
byte[] data,
long id
) : base( type, data ) {
this.id = id;
}
public override string ToString () => $"{{\n type: \"{type}\",\n id: {id},\n data: {System.Text.Encoding.UTF8.GetString( data )}\n}}";
}
[Serializable]
public class ConductorRequest : WireMessage {
public ConductorRequest ( byte[] data, long id )
: base( type: "Request", data: data, id: id ) { }
}
[Serializable]
public class ConductorResponse {
public string type;
public byte[] data;
public long id;
}
[Serializable]
public class CallZomeRequest : RustEnum<CallZomeRequest.Data> {
[Serializable]
public struct Data {
public byte[][] cell_id;
public string zome_name;
public string fn_name;
public byte[] payload;
public byte[] cap;
public byte[] provenance;
}
public CallZomeRequest (
byte[] dnaHash,
byte[] agentPubKey,
string zomeName,
string functionName,
byte[] payload,
byte[] provenance
) : base(
type: "zome_call",
data: new Data {
cell_id = CellID.Create( dnaHash, agentPubKey ),
zome_name = zomeName,
fn_name = functionName,
payload = payload,
cap = null,
provenance = provenance
}
) { }
}
public abstract class Conductor {
// public methods
public async void OpenConnection ( int port ) {
var url = $"ws://localhost:{port}";
websocket = new WebSocket( url );
websocket.OnOpen += OnOpen;
websocket.OnMessage += OnMessage;
websocket.OnError += OnError;
websocket.OnClose += OnClose;
Debug.Log( $"Holochain: Opening connection to conductor at `{url}`" );
await websocket.Connect();
await websocket.Receive();
}
public void DispatchMessageQueue () {
#if !UNITY_WEBGL || UNITY_EDITOR
websocket.DispatchMessageQueue();
#endif
}
public async void CloseConnection () {
await websocket.Close();
}
// internal requests
private long _requestId = 1;
protected WebSocket websocket;
protected async void SendRequest ( byte[] payload ) {
var formatter = new MessagePackFormatter();
var request = new ConductorRequest(
id: _requestId++,
data: payload
);
var packedRequest = formatter.Serialize( request );
Debug.Log( $"Holochain: Sending request\n{request.ToString()}" );
await websocket.Send( packedRequest );
Debug.Log( $"Holochain: Conductor sent request\n{System.Text.Encoding.UTF8.GetString( packedRequest )}" );
}
// protected handlers
protected abstract void OnOpen ();
protected abstract void OnMessage ( byte[] bytes );
protected abstract void OnError ( string error );
protected abstract void OnClose ( WebSocketCloseCode code );
}
public class AdminConductor : Conductor {
public AdminConductor () => OpenConnection( port: 63125 );
protected override void OnOpen () => Debug.Log( "Holochain: AdminConductor websocket connection opened" );
protected override void OnMessage ( byte[] bytes ) => Debug.Log( $"Holochain: AdminConductor received bytes:\n{bytes}" );
protected override void OnError ( string error ) => Debug.Log( $"Holochain: AdminConductor received error:\n{error}" );
protected override void OnClose ( WebSocketCloseCode code ) => Debug.Log( $"Holochain: AdminConductor closed with code {code}" );
}
public class AppConductor : Conductor {
// constructor
public AppConductor ( byte[] dnaHash, byte[] agentPubKey ) {
this.dnaHash = dnaHash;
this.agentPubKey = agentPubKey;
OpenConnection( port: 8888 );
}
// public methods
public void CallFunction ( string zome, string function, byte[] payload ) {
if ( websocket.State != WebSocketState.Open ) {
Debug.Log( $"Holochain: AppConductor couldn't call `{zome}/{function},` websocket connection not open" );
return;
}
var formatter = new MessagePackFormatter();
var zomeCallPayload = formatter.Serialize<CallZomeRequest>(
new CallZomeRequest(
dnaHash: dnaHash,
agentPubKey: agentPubKey,
zomeName: zome,
functionName: function,
payload: payload,
provenance: agentPubKey
)
);
SendRequest( zomeCallPayload );
}
// event handlers
protected override void OnOpen ()
=> Debug.Log( "Holochain: AppConductor websocket connection opened" );
protected override void OnError ( string error )
=> Debug.Log( $"Holochain: AppConductor received error: {error}" );
protected override void OnClose ( WebSocketCloseCode code )
=> Debug.Log( $"Holochain: AppConductor closed with code {code}" );
protected override void OnMessage ( byte[] bytes ) {
try {
Debug.Log( $"Holochain: AppConductor received msgpack bytes: {System.Text.Encoding.UTF8.GetString( bytes )}" );
var formatter = new MessagePackFormatter();
var response = formatter.Deserialize<ConductorResponse>( bytes );
Debug.Log( $"Holochain: AppConductor received response:\n{response}" );
try {
var content = formatter.Deserialize<Explorer.Client.App.MyOutput>( response.data );
Debug.Log( $"Holochain: AppConductor received content:\n{content}" );
} catch {
Debug.Log( "Holochain: Couldn't deserialize content bytes into string" );
}
} catch {
Debug.Log( "Holochain: Couldn't deserialize received message bytes into ConductorResponse" );
}
}
// private data
private byte[] dnaHash;
private byte[] agentPubKey;
}
}
use hdk::prelude::*;
#[derive(Serialize, Deserialize, Debug)]
pub struct SomeExternalInput {
first_name: String,
last_name: String,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct SomeExternalOutput {
output: String,
}
#[hdk_extern]
pub fn say_my_name(external_input: SomeExternalInput) -> ExternResult<SomeExternalOutput> {
let message: String = format!(
"Your name is {} {}",
external_input.first_name, external_input.last_name
);
let output: SomeExternalOutput = SomeExternalOutput { output: message };
Ok(output)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment