Skip to content

Instantly share code, notes, and snippets.

@campd
Last active November 21, 2021 21:13

Revisions

  1. campd revised this gist Sep 18, 2014. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions writing-actors.md
    Original file line number Diff line number Diff line change
    @@ -50,7 +50,7 @@ Here's a simple Hello World actor. It is a global actor (not associated with a
    This actor now supports a `sayHello` request. A request/reply will look like this:

    -> { to: <actorID>, type: "sayHello" }
    <- { from: <actorID> }
    <- { from: <actorID>, greeting: "hello" }

    Now we can create a client side object. We call these *front* objects.

    @@ -94,7 +94,7 @@ Arguments
    response: { echoed: RetVal("string") }
    })

    This tells the library to place the 0th argument, which should be a string, in the `echo` property of the response packet.
    This tells the library to place the 0th argument, which should be a string, in the `echo` property of the request packet.


    This will generate a request handler whose request and response packets look like this:
  2. campd revised this gist Aug 26, 2013. 1 changed file with 6 additions and 6 deletions.
    12 changes: 6 additions & 6 deletions writing-actors.md
    Original file line number Diff line number Diff line change
    @@ -457,7 +457,7 @@ Other than that, the basic protocol makes no guarantees about lifetime. Each in

    The protocol library will maintain the child/parent relationships for you, but it needs some help deciding what the child/parent relationships are.

    The default parent of an object is the first object that returns it after it is created. So to revisit our earlier SimpleActor `getChild` implementation:
    The default parent of an object is the first object that returns it after it is created. So to revisit our earlier HelloActor `getChild` implementation:

    getChild: method(function(id) {
    return new ChildActor(this.conn, id);
    @@ -466,9 +466,9 @@ The default parent of an object is the first object that returns it after it is
    response: { child: RetVal("childActor") }
    });

    The ChildActor's parent is the SimpleActor, because it's the one that created it.
    The ChildActor's parent is the HelloActor, because it's the one that created it.

    You can customize this behavior in two ways. The first is by defining a `defaultParent` property in your actor. Imagine a new ChildActor method:
    You can customize this behavior in two ways. The first is by defining a `marshallPool` property in your actor. Imagine a new ChildActor method:

    getSibling: method(function(id) {
    return new ChildActor(this.conn, id);
    @@ -477,13 +477,13 @@ You can customize this behavior in two ways. The first is by defining a `defaul
    response: { child: RetVal("childActor") }
    });

    This creates a new child actor owned by the current child actor. But in this example we want all actors created by the child to be owned by the SimpleActor. So we can define a `defaultParent` property that makes use of the `parent` proeprty provided by the Actor class:
    This creates a new child actor owned by the current child actor. But in this example we want all actors created by the child to be owned by the HelloActor. So we can define a `defaultParent` property that makes use of the `parent` proeprty provided by the Actor class:

    get defaultParent() { return this.parent }
    get marshallPool() { return this.parent }

    The front needs to provide a matching `defaultParent` property that returns an owning front, to make sure the client and server lifetimes stay synced.

    For more complex situations, you can define your own lifetime properties. Take this new pair of SimpleActor methods:
    For more complex situations, you can define your own lifetime properties. Take this new pair of HelloActor methods:

    // When the "temp" lifetime is specified, look for the _temporaryParent attribute as the owner.
    types.addLifetime("temp", "_temporaryParent");
  3. campd revised this gist Aug 26, 2013. 1 changed file with 16 additions and 15 deletions.
    31 changes: 16 additions & 15 deletions writing-actors.md
    Original file line number Diff line number Diff line change
    @@ -244,9 +244,10 @@ Probably the most common objects that need custom martialing are actors themselv
    actorType: "childActor",
    initialize: function(conn, id) {
    protocol.Actor.prototype.initialize.call(this, conn);
    this.greeting = "hello from " + id;
    },
    getGreeting: method(function() {
    return "hello from " + this.id;
    return this.greeting;
    }, {
    response: { greeting: RetVal("string") },
    }
    @@ -272,45 +273,45 @@ So we can now add the following code to HelloActor:
    front.getChild("child1").then(childFront => {
    return childFront.getGreeting();
    }).then(greeting => {
    assert(id == "hello from child1");
    assert(id === "hello from child1");
    });

    The conversation will look like this:

    { to: <actorID>, type: "getChild", id: "child1" }
    { from: <actorID>, child: { actor: <childActorID> }}
    { to: <childActorID>, type: "getID" }
    { from: <childActorID>, id: "child1" }
    { to: <childActorID>, type: "getGreeting" }
    { from: <childActorID>, greeting: "hello from child1" }

    But the ID is the only interesting part of this made-up example. You're never going to want a reference to a ChildActor without checking its ID. Making an extra request just to get that id is wasteful. You really want the first response to look like `{ from: <actorID>, child: { actor: <childActorID>, id: "child1" } }`
    But the ID is the only interesting part of this made-up example. You're never going to want a reference to a ChildActor without checking its ID. Making an extra request just to get that id is wasteful. You really want the first response to look like `{ from: <actorID>, child: { actor: <childActorID>, greeting: "hello from child1" } }`

    You can customize the marshalling of an actor by providing a `form` method in the `ChildActor` class:

    form: function() {
    return {
    actor: this.actorID,
    id: this.id
    greeting: this.greeting
    }
    },

    And you can demarshal in the `ChildFront` class by implementing a matching `form` method:

    form: function(form) {
    this.actorID = form.actor;
    this.id = form.id;
    this.greeting = form.greeting;
    }

    Now you can use the id immediately:

    front.getChild("child1").then(child => { assert(child.id === "child1) });
    front.getChild("child1").then(child => { assert(child.greeting === "child1) });

    You may come across a situation where you want to customize the output of a `form` method depending on the operation being performed. For example, imagine that ChildActor is a bit more complex, with a, b, c, and d members:

    ChildActor:
    form: function() {
    return {
    actor: this.actorID,
    id: this.id,
    greeting: this.greeting,
    a: this.a,
    b: this.b,
    c: this.c,
    @@ -349,7 +350,7 @@ And imagine you want to change 'c' and return the object:

    Now our response will look like:

    { from: <childActorID>, self: { actor: <childActorID>, id: <id>, a: <a>, b: <b>, c: "hello", d: <d> }
    { from: <childActorID>, self: { actor: <childActorID>, greeting: <id>, a: <a>, b: <b>, c: "hello", d: <d> }

    But that's wasteful. Only c changed. So we can provide a *detail* to the type using `#`:

    @@ -409,9 +410,9 @@ Actors are subclasses of jetpack `EventTarget`, so you can just emit:
    And now you can listen to events on a front:

    front.on("good-news", news => {
    dump("Got good news: " + news + "\n");
    console.log("Got good news: " + news + "\n");
    });
    front.giveGoodNews().then(() => { dump("request returned.") });
    front.giveGoodNews().then(() => { console.log("request returned.") });

    You might want to update your front's state when an event is fired, before emitting it against the front. You can use `preEvent` in the front definition for that:

    @@ -424,7 +425,7 @@ On a somewhat related note, not every method needs to be request/response. Just
    giveGoodNews: method(function(news) {
    emit(this, "good-news", news);
    }, {
    request: { news: Arg(0) },
    request: { news: Arg(0, "string") },
    oneway: true
    });

    @@ -436,7 +437,7 @@ No, let's talk about custom front methods instead.
    Custom Front Methods
    --------------------

    You might have some bookkeeping to do before issuing a request. Let's say you're calling that echo from way back, but you want to count the number of times you issue that request. Just use the `custom` tag in your front implementation:
    You might have some bookkeeping to do before issuing a request. Let's say you're calling `echo`, but you want to count the number of times you issue that request. Just use the `custom` tag in your front implementation:

    echo: custom(function(str) {
    this.numEchos++;
    @@ -450,7 +451,7 @@ This puts the generated implementation in `_echo` instead of `echo`, letting you
    Lifetimes
    ---------

    OK, I can't think of any more ways to procrastinate. The remote debugging protocol has the concept of a *parent* for each actor. This is to make distributed memory management a bit easier. Basically, any descendents of an actor will be destroyed if the actor is destroyed.
    OK, I can't think of any more ways to put this off. The remote debugging protocol has the concept of a *parent* for each actor. This is to make distributed memory management a bit easier. Basically, any descendents of an actor will be destroyed if the actor is destroyed.

    Other than that, the basic protocol makes no guarantees about lifetime. Each interface defined in the protocol will need to discuss and document its approach to lifetime management (although there are a few common patterns).

  4. campd revised this gist Aug 26, 2013. 1 changed file with 10 additions and 10 deletions.
    20 changes: 10 additions & 10 deletions writing-actors.md
    Original file line number Diff line number Diff line change
    @@ -197,7 +197,7 @@ Moving right along, let's say you want to pass/return an array of Incrementors.
    return incrementors;
    }, {
    request: { incrementors: Arg(0, "array:incrementor") },
    response: { incrementors: ReturnVal("array:incrementor") }
    response: { incrementors: RetVal("array:incrementor") }
    })

    Or maybe you want to return a dictionary where one item is a incrementor. To do this you need to tell the type system which members of the dictionary need custom marshallers:
    @@ -214,7 +214,7 @@ Or maybe you want to return a dictionary where one item is a incrementor. To do
    incrementor: new Incrementor(1), incrementorArray: [new Incrementor(2), new Incrementor(3)]
    }
    }, {
    response: ReturnVal("contrivedObject")
    response: RetVal("contrivedObject")
    });

    front.reallyContrivedExample().then(obj => {
    @@ -263,10 +263,10 @@ The library will register a marshaller for the actor type itself, using typeName
    So we can now add the following code to HelloActor:

    getChild: method(function(id) {
    return new ChildActor(this.conn, id);
    return ChildActor(this.conn, id);
    }, {
    request: { id: Arg(0, "string") },
    response: { child: ReturnVal("childActor") }
    response: { child: RetVal("childActor") }
    });

    front.getChild("child1").then(childFront => {
    @@ -340,7 +340,7 @@ And imagine you want to change 'c' and return the object:
    return this;
    }, {
    request: { newC: Arg(0) },
    response: { self: ReturnVal("childActor") }
    response: { self: RetVal("childActor") }
    });

    ...
    @@ -353,7 +353,7 @@ Now our response will look like:

    But that's wasteful. Only c changed. So we can provide a *detail* to the type using `#`:

    response: { self: ReturnVal("childActor#changec") }
    response: { self: RetVal("childActor#changec") }

    and update our form methods to make use of that data:

    @@ -462,7 +462,7 @@ The default parent of an object is the first object that returns it after it is
    return new ChildActor(this.conn, id);
    }, {
    request: { id: Arg(0) },
    response: { child: ReturnVal("childActor") }
    response: { child: RetVal("childActor") }
    });

    The ChildActor's parent is the SimpleActor, because it's the one that created it.
    @@ -473,7 +473,7 @@ You can customize this behavior in two ways. The first is by defining a `defaul
    return new ChildActor(this.conn, id);
    }, {
    request: { id: Arg(0) },
    response: { child: ReturnVal("childActor") }
    response: { child: RetVal("childActor") }
    });

    This creates a new child actor owned by the current child actor. But in this example we want all actors created by the child to be owned by the SimpleActor. So we can define a `defaultParent` property that makes use of the `parent` proeprty provided by the Actor class:
    @@ -497,7 +497,7 @@ For more complex situations, you can define your own lifetime properties. Take
    }, {
    request: { id: Arg(0) },
    response: {
    child: ReturnVal("temp:childActor") // use the lifetime name here to specify the expected lifetime.
    child: RetVal("temp:childActor") // use the lifetime name here to specify the expected lifetime.
    }
    });

    @@ -538,7 +538,7 @@ You can specify a telemetry probe id in your method spec:
    return str;
    }, {
    request: { str: Arg(0) },
    response: { str: ReturnVal() },
    response: { str: RetVal() },
    telemetry: "ECHO"
    });

  5. campd revised this gist Aug 26, 2013. 1 changed file with 7 additions and 7 deletions.
    14 changes: 7 additions & 7 deletions writing-actors.md
    Original file line number Diff line number Diff line change
    @@ -245,10 +245,10 @@ Probably the most common objects that need custom martialing are actors themselv
    initialize: function(conn, id) {
    protocol.Actor.prototype.initialize.call(this, conn);
    },
    getID: method(function() {
    return this.id;
    getGreeting: method(function() {
    return "hello from " + this.id;
    }, {
    response: { id: ReturnVal() },
    response: { greeting: RetVal("string") },
    }
    });

    @@ -270,9 +270,9 @@ So we can now add the following code to HelloActor:
    });

    front.getChild("child1").then(childFront => {
    return childFront.getID();
    }).then(id => {
    assert(id == "child1");
    return childFront.getGreeting();
    }).then(greeting => {
    assert(id == "hello from child1");
    });

    The conversation will look like this:
    @@ -282,7 +282,7 @@ The conversation will look like this:
    { to: <childActorID>, type: "getID" }
    { from: <childActorID>, id: "child1" }

    But the ID is pretty core to this made-up example. You're never going to want a reference to a ChildActor without checking its ID. Making an extra request just to get the id is wasteful. You really want the first response to look like `{ from: <actorID>, child: { actor: <childActorID>, id: "child1" } }`
    But the ID is the only interesting part of this made-up example. You're never going to want a reference to a ChildActor without checking its ID. Making an extra request just to get that id is wasteful. You really want the first response to look like `{ from: <actorID>, child: { actor: <childActorID>, id: "child1" } }`

    You can customize the marshalling of an actor by providing a `form` method in the `ChildActor` class:

  6. campd revised this gist Aug 26, 2013. 1 changed file with 4 additions and 2 deletions.
    6 changes: 4 additions & 2 deletions writing-actors.md
    Original file line number Diff line number Diff line change
    @@ -265,12 +265,14 @@ So we can now add the following code to HelloActor:
    getChild: method(function(id) {
    return new ChildActor(this.conn, id);
    }, {
    request: { id: Arg(0) },
    request: { id: Arg(0, "string") },
    response: { child: ReturnVal("childActor") }
    });

    front.getChild("child1").then(childFront => {
    childFront.getID().then(id => { assert(id == "child1"); });
    return childFront.getID();
    }).then(id => {
    assert(id == "child1");
    });

    The conversation will look like this:
  7. campd revised this gist Aug 26, 2013. 1 changed file with 3 additions and 3 deletions.
    6 changes: 3 additions & 3 deletions writing-actors.md
    Original file line number Diff line number Diff line change
    @@ -238,7 +238,7 @@ If an argument, return value, or dict property can be null/undefined, you can pr
    Actors
    ------

    Probably the most common objects that need custom martialing are actors themselves. These are more interesting than the Incrementor object, but by default they're somewhat easy to work with. Let's add a ChildActor implementation that will be returned by the SimpleActor (which is rapidly becoming the OverwhelminglyComplexActor):
    Probably the most common objects that need custom martialing are actors themselves. These are more interesting than the Incrementor object, but by default they're somewhat easy to work with. Let's add a ChildActor implementation that will be returned by the HelloActor (which is rapidly becoming the OverwhelminglyComplexActor):

    let ChildActor = protocol.ActorClass({
    actorType: "childActor",
    @@ -258,9 +258,9 @@ Probably the most common objects that need custom martialing are actors themselv
    },
    });

    The library will register a marshaller for the actor type itself, using typeName as its tag (I told you I'd explain that eventually).
    The library will register a marshaller for the actor type itself, using typeName as its tag.

    So we can now add the following code to SimpleActor:
    So we can now add the following code to HelloActor:

    getChild: method(function(id) {
    return new ChildActor(this.conn, id);
  8. campd revised this gist Aug 26, 2013. No changes.
  9. campd revised this gist Aug 26, 2013. 1 changed file with 24 additions and 14 deletions.
    38 changes: 24 additions & 14 deletions writing-actors.md
    Original file line number Diff line number Diff line change
    @@ -170,16 +170,16 @@ I want that response to look like `{ from: <actorID>, value: <number> }`, but th

    And now our client can use the API as expected:

    front.getIncrementor(5).then(wrapper => {
    wrapper.increment();
    assert(wrapper.value === 6);
    front.getIncrementor(5).then(incrementor => {
    incrementor.increment();
    assert(incrementor.value === 6);
    });

    You can do the same thing with arguments:

    passIncrementor: method(function(inc) {
    w.increment();
    assert(wrapper.value === 6);
    assert(incrementor.value === 6);
    }, {
    request: { Arg(0, "incrementor") },
    });
    @@ -196,22 +196,22 @@ Moving right along, let's say you want to pass/return an array of Incrementors.
    }
    return incrementors;
    }, {
    request: { wrappers: Arg(0, "array:incrementor") },
    response: { wrappers: ReturnVal("array:incrementor") }
    request: { incrementors: Arg(0, "array:incrementor") },
    response: { incrementors: ReturnVal("array:incrementor") }
    })

    Or maybe you want to return a dictionary where one item is a wrapper. To do this you need to tell the type system which members of the dictionary need custom marshallers:
    Or maybe you want to return a dictionary where one item is a incrementor. To do this you need to tell the type system which members of the dictionary need custom marshallers:

    protocol.types.addDictType("contrivedObject", {
    wrapper: "numberWrapper",
    wrapperArray: "array:numberwrapper"
    incrementor: "incrementor",
    incrementorArray: "array:incrementor"
    });

    reallyContrivedExample: method(function() {
    return {
    /* a and b are primitives and so don't need to be called out specifically in addDictType */
    a: "hello", b: "world",
    wrapper: new NumberWrapper(1), wrapperArray: [new NumberWrapper(2), new NumberWrapper(3)]
    incrementor: new Incrementor(1), incrementorArray: [new Incrementor(2), new Incrementor(3)]
    }
    }, {
    response: ReturnVal("contrivedObject")
    @@ -220,15 +220,25 @@ Or maybe you want to return a dictionary where one item is a wrapper. To do thi
    front.reallyContrivedExample().then(obj => {
    assert(obj.a == "hello");
    assert(obj.b == "world");
    assert(wrapper.i == 1);
    assert(wrapperArray[0].i == 2);
    assert(wrapperArray[1].i == 3);
    assert(incrementor.i == 1);
    assert(incrementorArray[0].i == 2);
    assert(incrementorArray[1].i == 3);
    });

    Nullables
    ---------

    If an argument, return value, or dict property can be null/undefined, you can prepend `nullable:` to the type name:

    "nullable:incrementor", // Can be null/undefined or an incrementor
    "array:nullable:incrementor", // An array of incrementors that can have holes.
    "nullable:array:incrementor" // Either null/undefined or an array of incrementors without holes.


    Actors
    ------

    Probably the most common objects that need custom martialing are actors themselves. These are more interesting than the NumberWrapper object, but by default they're somewhat easy to work with. Let's add a ChildActor implementation that will be returned by the SimpleActor (which is rapidly becoming the OverwhelminglyComplexActor):
    Probably the most common objects that need custom martialing are actors themselves. These are more interesting than the Incrementor object, but by default they're somewhat easy to work with. Let's add a ChildActor implementation that will be returned by the SimpleActor (which is rapidly becoming the OverwhelminglyComplexActor):

    let ChildActor = protocol.ActorClass({
    actorType: "childActor",
  10. campd revised this gist Aug 26, 2013. 1 changed file with 11 additions and 12 deletions.
    23 changes: 11 additions & 12 deletions writing-actors.md
    Original file line number Diff line number Diff line change
    @@ -184,24 +184,23 @@ You can do the same thing with arguments:
    request: { Arg(0, "incrementor") },
    });

    front.passWrapper(new Incrementor(5));
    front.passIncrementor(new Incrementor(5));

    As an aside, if you want to be a bit more verbose in the interest of self-documenting code, there are `boolean`, `number`, and `string` types defined as aliases to the `primitive` type, so you could use `Arg(0, "boolean")`, which is no different really than `Arg(0)` except that it's more clear what you want.
    The library provides primitiive `boolean`, `number`, `string`, and `json` types.

    Moving right along, let's say you want to pass/return an array of NumberWrappers. You can just prepend `array:` to the type name:
    Moving right along, let's say you want to pass/return an array of Incrementors. You can just prepend `array:` to the type name:

    incrementWrapperArray: method(function(wrappers) {
    return wrappers.map(wrapper => {
    let newWrap = new NumberWrapper(wrapper.i);
    newWrap.increment();
    return newWrap;
    })
    incrementAll: method(function(incrementors) {
    incrementors.forEach(incrementor => {
    incrementor.increment();
    }
    return incrementors;
    }, {
    request: { wrappers: Arg(0, "array:numberWrapper") },
    response: { wrappers: ReturnVal("array:numberWrapper") }
    request: { wrappers: Arg(0, "array:incrementor") },
    response: { wrappers: ReturnVal("array:incrementor") }
    })

    Or maybe you want to return a dictionary with one item being a wrapper. To do this you need to tell the type system which members of the dictionary need custom marshallers:
    Or maybe you want to return a dictionary where one item is a wrapper. To do this you need to tell the type system which members of the dictionary need custom marshallers:

    protocol.types.addDictType("contrivedObject", {
    wrapper: "numberWrapper",
  11. campd revised this gist Aug 26, 2013. 1 changed file with 5 additions and 4 deletions.
    9 changes: 5 additions & 4 deletions writing-actors.md
    Original file line number Diff line number Diff line change
    @@ -172,18 +172,19 @@ And now our client can use the API as expected:

    front.getIncrementor(5).then(wrapper => {
    wrapper.increment();
    assert(wrapper.value === 6)
    assert(wrapper.value === 6);
    });

    You can do the same thing with arguments:

    passWrapper: method(function(w) {
    passIncrementor: method(function(inc) {
    w.increment();
    assert(wrapper.value === 6);
    }, {
    request: { Arg(0, "numberWrapper") },
    request: { Arg(0, "incrementor") },
    });

    front.passWrapper(new NumberWrapper(5));
    front.passWrapper(new Incrementor(5));

    As an aside, if you want to be a bit more verbose in the interest of self-documenting code, there are `boolean`, `number`, and `string` types defined as aliases to the `primitive` type, so you could use `Arg(0, "boolean")`, which is no different really than `Arg(0)` except that it's more clear what you want.

  12. campd revised this gist Aug 26, 2013. 1 changed file with 24 additions and 23 deletions.
    47 changes: 24 additions & 23 deletions writing-actors.md
    Original file line number Diff line number Diff line change
    @@ -135,44 +135,45 @@ and now your packet will look like:
    Types and Marshalling
    ---------------------

    Things have been pretty simple up to this point - all the arguments we've passed in have been javascript primitives, so we haven't needed to specify their type. But for some types (most importantly Actor types, which I'll get to eventually), we can't just copy them into a JSON packet and expect it to work, we need to marshal things ourselves.
    Things have been pretty simple up to this point - all the arguments we've passed in have been javascript primitives. But for some types (most importantly Actor types, which I'll get to eventually), we can't just copy them into a JSON packet and expect it to work, we need to marshal things ourselves.

    Again, the protocol lib tries hard to provide a natural API to actors and clients, and sometime that natural API might involve object APIs. I'm going to use a wickedly contrived example, bear with me. Let's say I have a small object that contains a number and has a few methods associated with it:

    let NumberWrapper = function(i) {
    this.i = i;
    };

    NumberWrapper.prototype = {
    increment: function() { this.i++ },
    decrement: function() { this.i-- }
    let Incrementor = function(i) {
    this.value = value;
    }
    Incrementor.prototype = {
    increment: function() { this.value++ },
    decrement: function() { this.value-- }
    };


    and I want to return it from a backend function:

    wrapNumber: method(function(i) {
    return new NumberWrapper(i)
    getIncrementor: method(function(i) {
    return new Incrementor(i)
    }, {
    request: { number: Arg(0) },
    response: { wrappedNumber: ReturnVal() }
    request: { number: Arg(0, "number") },
    response: { value: RetVal("incrementor") } // We'll define "incrementor" below.
    });

    I want that response to look like `{ from: <actorID>, wrappedNumber: <number> }`, but the client side needs to know to return a NumberWrapper, not a primitive number. So let's tell the protocol lib about wrapped numbers:

    protocol.types.addType("numberWrapper", {
    write: (v) => v.i,
    read: (v) => new NumberWrapper(v)
    });
    I want that response to look like `{ from: <actorID>, value: <number> }`, but the client side needs to know to return an Incrementor, not a primitive number. So let's tell the protocol lib about Incrementors:

    and then change the response line to let the library know to use this wrapper:
    protocol.types.addType("incrementor", {
    // When writing to a protocol packet, just send the value
    write: (v) => v.value,

    response: { wrappedNumber: ReturnVal("numberWrapper") }
    // When reading from a protocol packet, wrap with an Incrementor
    // object.
    read: (v) => new Incrementor(v)
    });

    And now our client can use the API as expected:

    front.wrapNumber(5).then(wrapper => {
    wrapper.increment() assert(wrapper.i === 6) }
    front.getIncrementor(5).then(wrapper => {
    wrapper.increment();
    assert(wrapper.value === 6)
    });

    You can do the same thing with arguments:

    @@ -257,7 +258,7 @@ So we can now add the following code to SimpleActor:
    request: { id: Arg(0) },
    response: { child: ReturnVal("childActor") }
    });

    front.getChild("child1").then(childFront => {
    childFront.getID().then(id => { assert(id == "child1"); });
    });
  13. campd revised this gist Aug 26, 2013. 1 changed file with 3 additions and 3 deletions.
    6 changes: 3 additions & 3 deletions writing-actors.md
    Original file line number Diff line number Diff line change
    @@ -108,8 +108,8 @@ The client usage should be predictable:

    The library tries hard to make using fronts feel like natural javascript (or as natural as you believe promises are, I guess). When building the response it will put the return value of the function where RetVal() is specified in the response template, and on the client side it will use the value in that position when resolving the promise.

    More Complicated Return Values
    ------------------------------
    Returning JSON
    --------------

    Maybe your response is an object:

    @@ -126,7 +126,7 @@ This will generate a response packet that looks like:

    That's probably unnecessary nesting (if you're sure you won't be returning an object with 'from' as a key!), so you can just replace `response` with:

    response: ReturnVal()
    response: RetVal("json")

    and now your packet will look like:

  14. campd revised this gist Aug 26, 2013. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions writing-actors.md
    Original file line number Diff line number Diff line change
    @@ -116,8 +116,8 @@ Maybe your response is an object:
    addOneTwice: method(function(a, b) {
    return { a: a + 1, b: b + 1 };
    }, {
    request: { a: Arg(0), b: Arg(1) }
    response: { ret: ReturnVal() }
    request: { a: Arg(0, "number"), b: Arg(1, "number") },
    response: { ret: RetVal("json") }
    });

    This will generate a response packet that looks like:
  15. campd revised this gist Aug 26, 2013. 1 changed file with 21 additions and 30 deletions.
    51 changes: 21 additions & 30 deletions writing-actors.md
    Original file line number Diff line number Diff line change
    @@ -72,53 +72,44 @@ So if we have a reference to a HelloFront object, we can issue a `sayHello` requ
    console.log(greeting);
    });

    How do you get an initial reference to the front?
    How do you get an initial reference to the front? That's a bit tricky, but basically there are two ways:

    * Manually
    * Magically

    So from the client side we need to create a *front* to interact with the actor. Here's a front for the SimpleBackend actor:
    Manually - If you're using a DebuggerClient instance, you can discover the actorID manually and create a Front for it:
    let hello = HelloFront(this.client, { actorID: <hello actorID> });

    let SimpleFront = protocol.FrontClass(SimpleBackend, {
    initialize: function(client, form) {
    protocol.Front.prototype.initialize.call(this, client, form);
    }
    });

    This front has the same problem as the actor, which is that I'm not telling you how to get the initial reference to the front. Sorry. But let's pretend you do have it, and want to say hello:
    Magically - Once you have an initial reference to a protocol.js object, it can return other protocol.js objects and fronts will automatically be created.

    let front = magicalGetSimpleFront();
    front.printHello().then(() => {
    dump("The backend said hello.");
    });

    "Curses," you say, "promises again." Yeah. If people are interested we could do a version using callbacks, I'm not religious.

    But really the more interesting thing is that the front has a sayHello method now, and it issues a request to the actor and waits for a response and all that good stuff.
    Arguments
    ---------

    So now we'll imagine that you want to do something a little bit more interesting than a no-argument-no-return function. Let's you want something that looks like:
    `sayHello` has no arguments, so let's add a method that does take arguments:

    echo: function(str) {
    echo: method(function(str) {
    return str + "... " + str + "...";
    }
    }, {
    request: { echo: Arg(0, "string") },
    response: { echoed: RetVal("string") }
    })

    This backend's getting fancy now. Once you start having arguments and returns, you need to describe how they should be marshalled to the protocol library:
    This tells the library to place the 0th argument, which should be a string, in the `echo` property of the response packet.

    echo: method(function(str) {
    return str + "... " + str + "..."; // so far no real changes
    }, {
    request: { echo: Arg(0) },
    response: { echoed: ReturnVal() }
    });

    This will generate a request handler whose request and response packets looks like this:
    This will generate a request handler whose request and response packets look like this:

    { to: <actorID>, type: "echo", echo: <str> }
    { from: <actorID>, echoed: <str> }

    The client usage should be predictable:

    front.echo("hello").then(str => { assert(str === "hello... hello...") })
    echo.echo("hello").then(str => { assert(str === "hello... hello...") })

    The library tries hard to make using fronts feel like natural javascript (or as natural as you believe promises are, I guess). When building the response it will put the return value of the function where RetVal() is specified in the response template, and on the client side it will use the value in that position when resolving the promise.

    The library tries hard to make using fronts feel like natural javascript (or as natural as you believe promises are, I guess). When building the response it will put the return value of the function where ReturnVal() is specified in the response template, and on the client side it will use the value in that position when resolving the promise.
    More Complicated Return Values
    ------------------------------

    Maybe your response is an object:

  16. campd revised this gist Aug 26, 2013. 1 changed file with 16 additions and 43 deletions.
    59 changes: 16 additions & 43 deletions writing-actors.md
    Original file line number Diff line number Diff line change
    @@ -47,60 +47,33 @@ Here's a simple Hello World actor. It is a global actor (not associated with a
    }
    });

    This actor now supports a `sayHello` request. A request/reply will look like this:

    -> { to: <actorID>, type: "sayHello" }
    <- { from: <actorID> }

    Starting out with primitive types
    ---------------------------------
    Now we can create a client side object. We call these *front* objects.

    So you have an idea for a Firefox developer tool. You (obviously) want it to work on Fennec and Firefox OS, so it needs to use the remote debugging protocol. You have a backend:
    Here's the front for the HelloActor:

    function SimpleBackend() { }
    SimpleBackend.prototype = {
    printHello: function() { dump("hello") }
    };

    and a frontend that's nicely tied in with the developer toolbox somehow. I don't know how, but it has access to a Target at least:

    function createBackend(target) {
    let backend = new SimpleBackend(target);
    backend.printHello();
    }

    I guess it's not the best idea for a developer tool.

    We want to make the backend into an actor. First we need to import the protocol library, and maybe destructure it a bit:

    let protocol = require("devtools/server/protocol");
    let {method, Arg, Option, ReturnVal} = protocol;

    Then we'll do a bit of magic to change backend into an actor. This uses the addon-sdk's heritage module behind the scenes, in case you want to understand the class stuff a bit better.

    let SimpleBackend = protocol.ActorClass({
    typeName: "simpleBackend", // I'll explain types later, I promise.
    initialize: function(conn) {
    protocol.Actor.prototype.initialize.call(this, conn); // the worst part of heritage.
    },

    printHello: function() { dump("hello") }
    let HelloFront = protocol.FrontClass(HelloActor, {
    initialize: function(client, form) {
    protocol.Front.prototype.initialize.call(this, client, form);
    }
    });

    `SimpleBackend` is an actor now, but we have two problems:

    1. We haven't hooked it up to the debugger server yet, so there's no way to create one, and
    2. It doesn't actually have any requests that can be made against it.
    Note that there is no `sayHello` method. The FrontClass will generate a method on the Front object that matches the method declaration in the Actor class.

    We really should fix #1 at some point, but it's messy and I don't want to explain it right now. Soon, maybe.
    The generated methods will return a Promise. That promise will resolve to the RetVal of the actor method.

    But fixing #2 should be easy. Replace `printHello` with:
    So if we have a reference to a HelloFront object, we can issue a `sayHello` request:

    printHello: method(function() {
    dump("hello");
    })
    hello.sayHello().then(greeting => {
    console.log(greeting);
    });

    and now it supports a `printHello` request type that has request and response packets that look like:
    How do you get an initial reference to the front?

    { to: <actorID>, type: 'printHello' }
    { from: <actorID> }

    So from the client side we need to create a *front* to interact with the actor. Here's a front for the SimpleBackend actor:

  17. campd revised this gist Aug 26, 2013. 1 changed file with 19 additions and 3 deletions.
    22 changes: 19 additions & 3 deletions writing-actors.md
    Original file line number Diff line number Diff line change
    @@ -9,8 +9,9 @@ Here's a simple Hello World actor. It is a global actor (not associated with a
    let protocol = require("devtools/server/protocol");
    let {method, Arg, Option, RetVal} = protocol;

    // This will be called by the framework when you call DebuggerServer.registerModule(), and
    // adds the actor as a 'helloActor' property on the root actor.
    // This will be called by the framework when you call DebuggerServer.
    // registerModule(), and adds the actor as a 'helloActor' property
    // on the root actor.
    exports.register = function(handle) {
    handle.addGlobalActor(HelloActor, "helloActor");
    }
    @@ -30,9 +31,24 @@ Here's a simple Hello World actor. It is a global actor (not associated with a
    protocol.Actor.prototype.initialize.call(this, conn); // This is the worst part of heritage.
    },

    printHello: function() { dump("hello") }
    sayHello: method(function() {
    return "hello";
    }, {
    // The request packet template. There are no arguments, so
    // it is empty. The framework will add the "type" and "to"
    // request properties.
    request: {},

    // The response packet template. The return value of the function
    // will be plugged in where the RetVal() appears in the template.
    response: {
    greeting: RetVal("string") // "string" is the return value type.
    }
    }
    });



    Starting out with primitive types
    ---------------------------------

  18. campd revised this gist Aug 26, 2013. 1 changed file with 3 additions and 2 deletions.
    5 changes: 3 additions & 2 deletions writing-actors.md
    Original file line number Diff line number Diff line change
    @@ -20,8 +20,9 @@ Here's a simple Hello World actor. It is a global actor (not associated with a
    handle.removeGlobalActor(HelloActor); // This is optional, all registered actors will be removed automatically
    }

    // Create the hello actor. This uses addon-sdk's heritage module behind the scenes
    // in case you want to understand the class stuff a bit better.
    // Create the hello actor. This uses addon-sdk's heritage module
    // behind the scenes in case you want to understand the class stuff
    // a bit better.

    let HelloActor = protocol.ActorClass({
    typeName: "helloWorld", // I'll explain types later, I promise.
  19. campd revised this gist Aug 26, 2013. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions writing-actors.md
    Original file line number Diff line number Diff line change
    @@ -1,5 +1,5 @@
    Writing a Firefox Remote Debugging Protocol Actor
    =================================================
    Writing an Actor
    ================

    A Simple Hello World
    --------------------
  20. campd revised this gist Aug 26, 2013. 1 changed file with 0 additions and 1 deletion.
    1 change: 0 additions & 1 deletion writing-actors.md
    Original file line number Diff line number Diff line change
    @@ -20,7 +20,6 @@ Here's a simple Hello World actor. It is a global actor (not associated with a
    handle.removeGlobalActor(HelloActor); // This is optional, all registered actors will be removed automatically
    }


    // Create the hello actor. This uses addon-sdk's heritage module behind the scenes
    // in case you want to understand the class stuff a bit better.

  21. campd revised this gist Aug 26, 2013. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions writing-actors.md
    Original file line number Diff line number Diff line change
    @@ -20,6 +20,7 @@ Here's a simple Hello World actor. It is a global actor (not associated with a
    handle.removeGlobalActor(HelloActor); // This is optional, all registered actors will be removed automatically
    }


    // Create the hello actor. This uses addon-sdk's heritage module behind the scenes
    // in case you want to understand the class stuff a bit better.

  22. campd revised this gist Aug 26, 2013. 1 changed file with 33 additions and 2 deletions.
    35 changes: 33 additions & 2 deletions writing-actors.md
    Original file line number Diff line number Diff line change
    @@ -1,5 +1,36 @@
    So you want to write an actor
    =============================
    Writing a Firefox Remote Debugging Protocol Actor
    =================================================

    A Simple Hello World
    --------------------

    Here's a simple Hello World actor. It is a global actor (not associated with a given browser tab).

    let protocol = require("devtools/server/protocol");
    let {method, Arg, Option, RetVal} = protocol;

    // This will be called by the framework when you call DebuggerServer.registerModule(), and
    // adds the actor as a 'helloActor' property on the root actor.
    exports.register = function(handle) {
    handle.addGlobalActor(HelloActor, "helloActor");
    }

    // This will be called by the framework during shutdown/unload.
    exports.unregister = function(handle) {
    handle.removeGlobalActor(HelloActor); // This is optional, all registered actors will be removed automatically
    }

    // Create the hello actor. This uses addon-sdk's heritage module behind the scenes
    // in case you want to understand the class stuff a bit better.

    let HelloActor = protocol.ActorClass({
    typeName: "helloWorld", // I'll explain types later, I promise.
    initialize: function(conn) {
    protocol.Actor.prototype.initialize.call(this, conn); // This is the worst part of heritage.
    },

    printHello: function() { dump("hello") }
    });

    Starting out with primitive types
    ---------------------------------
  23. campd revised this gist Aug 26, 2013. No changes.
  24. campd revised this gist Apr 25, 2013. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion writing-actors.md
    Original file line number Diff line number Diff line change
    @@ -65,7 +65,7 @@ So from the client side we need to create a *front* to interact with the actor.
    This front has the same problem as the actor, which is that I'm not telling you how to get the initial reference to the front. Sorry. But let's pretend you do have it, and want to say hello:

    let front = magicalGetSimpleFront();
    front.sayHello().then(() => {
    front.printHello().then(() => {
    dump("The backend said hello.");
    });

  25. Dave Camp revised this gist Apr 25, 2013. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion writing-actors.md
    Original file line number Diff line number Diff line change
    @@ -6,7 +6,7 @@ Starting out with primitive types

    So you have an idea for a Firefox developer tool. You (obviously) want it to work on Fennec and Firefox OS, so it needs to use the remote debugging protocol. You have a backend:

    function SimpleBackend(target) { this.target() }
    function SimpleBackend() { }
    SimpleBackend.prototype = {
    printHello: function() { dump("hello") }
    };
  26. campd revised this gist Apr 25, 2013. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion writing-actors.md
    Original file line number Diff line number Diff line change
    @@ -6,7 +6,7 @@ Starting out with primitive types

    So you have an idea for a Firefox developer tool. You (obviously) want it to work on Fennec and Firefox OS, so it needs to use the remote debugging protocol. You have a backend:

    function SimpleBackend(target) { this.target() }
    function SimpleBackend() { }
    SimpleBackend.prototype = {
    printHello: function() { dump("hello") }
    };
  27. @jimblandy jimblandy revised this gist Apr 25, 2013. 1 changed file with 3 additions and 3 deletions.
    6 changes: 3 additions & 3 deletions writing-actors.md
    Original file line number Diff line number Diff line change
    @@ -76,13 +76,13 @@ But really the more interesting thing is that the front has a sayHello method no
    So now we'll imagine that you want to do something a little bit more interesting than a no-argument-no-return function. Let's you want something that looks like:

    echo: function(str) {
    return str;
    return str + "... " + str + "...";
    }

    This backend's getting fancy now. Once you start having arguments and returns, you need to describe how they should be marshalled to the protocol library:

    echo: method(function(str) {
    return str; // so far no real changes
    return str + "... " + str + "..."; // so far no real changes
    }, {
    request: { echo: Arg(0) },
    response: { echoed: ReturnVal() }
    @@ -95,7 +95,7 @@ This will generate a request handler whose request and response packets looks li

    The client usage should be predictable:

    front.echo("hello").then(str => { assert(str === "hello") })
    front.echo("hello").then(str => { assert(str === "hello... hello...") })

    The library tries hard to make using fronts feel like natural javascript (or as natural as you believe promises are, I guess). When building the response it will put the return value of the function where ReturnVal() is specified in the response template, and on the client side it will use the value in that position when resolving the promise.

  28. Dave Camp revised this gist Apr 25, 2013. 1 changed file with 51 additions and 43 deletions.
    94 changes: 51 additions & 43 deletions writing-actors.md
    Original file line number Diff line number Diff line change
    @@ -10,14 +10,14 @@ So you have an idea for a Firefox developer tool. You (obviously) want it to wo
    SimpleBackend.prototype = {
    printHello: function() { dump("hello") }
    };

    and a frontend that's nicely tied in with the developer toolbox somehow. I don't know how, but it has access to a Target at least:

    function createBackend(target) {
    let backend = new SimpleBackend(target);
    backend.printHello();
    }

    I guess it's not the best idea for a developer tool.

    We want to make the backend into an actor. First we need to import the protocol library, and maybe destructure it a bit:
    @@ -32,7 +32,7 @@ Then we'll do a bit of magic to change backend into an actor. This uses the add
    initialize: function(conn) {
    protocol.Actor.prototype.initialize.call(this, conn); // the worst part of heritage.
    },

    printHello: function() { dump("hello") }
    });

    @@ -48,20 +48,20 @@ But fixing #2 should be easy. Replace `printHello` with:
    printHello: method(function() {
    dump("hello");
    })

    and now it supports a `printHello` request type that has request and response packets that look like:

    { to: <actorID>, type: 'printHello' }
    { from: <actorID> }

    So from the client side we need to create a *front* to interact with the actor. Here's a front for the SimpleBackend actor:

    let SimpleFront = protocol.FrontClass(SimpleBackend, {
    initialize: function(client, form) {
    protocol.Front.prototype.initialize.call(this, client, form);
    }
    });

    This front has the same problem as the actor, which is that I'm not telling you how to get the initial reference to the front. Sorry. But let's pretend you do have it, and want to say hello:

    let front = magicalGetSimpleFront();
    @@ -92,7 +92,7 @@ This will generate a request handler whose request and response packets looks li

    { to: <actorID>, type: "echo", echo: <str> }
    { from: <actorID>, echoed: <str> }

    The client usage should be predictable:

    front.echo("hello").then(str => { assert(str === "hello") })
    @@ -102,23 +102,23 @@ The library tries hard to make using fronts feel like natural javascript (or as
    Maybe your response is an object:

    addOneTwice: method(function(a, b) {
    return { a + 1, b + 1 };
    return { a: a + 1, b: b + 1 };
    }, {
    request: { a: Arg(0), b: Arg(1) }
    response: { ret: ReturnVal() }
    });

    This will generate a response packet that looks like:

    { from: <actorID>, ret: { a: <number>, b: <number> } }

    That's probably unnecessary nesting (if you're sure you won't be returning an object with 'from' as a key!), so you can just replace `response` with:

    response: ReturnVal()

    and now your packet will look like:
    { from: <actorID>, a: <number>, b: number }

    { from: <actorID>, a: <number>, b: <number> }

    Types and Marshalling
    ---------------------
    @@ -127,9 +127,16 @@ Things have been pretty simple up to this point - all the arguments we've passed

    Again, the protocol lib tries hard to provide a natural API to actors and clients, and sometime that natural API might involve object APIs. I'm going to use a wickedly contrived example, bear with me. Let's say I have a small object that contains a number and has a few methods associated with it:

    let NumberWrapper = function(i) { this.i }
    NumberWrapper.prototype = { increment: function() { i++ }, decrement: function() { i-- } };

    let NumberWrapper = function(i) {
    this.i = i;
    };

    NumberWrapper.prototype = {
    increment: function() { this.i++ },
    decrement: function() { this.i-- }
    };


    and I want to return it from a backend function:

    wrapNumber: method(function(i) {
    @@ -142,17 +149,18 @@ and I want to return it from a backend function:
    I want that response to look like `{ from: <actorID>, wrappedNumber: <number> }`, but the client side needs to know to return a NumberWrapper, not a primitive number. So let's tell the protocol lib about wrapped numbers:

    protocol.types.addType("numberWrapper", {
    write: (v) => { return v.i },
    read: (v) => { return new NumberWrapper(v) }
    write: (v) => v.i,
    read: (v) => new NumberWrapper(v)
    });

    and then change the response line to let the library know to use this wrapper:

    response: { wrappedNumber: ReturnVal("numberWrapper") }

    And now our client can use the API as expected:

    front.wrapNumber(5).then(wrapper => { wrapper.increment() assert(wrapper.i === 6) }
    front.wrapNumber(5).then(wrapper => {
    wrapper.increment() assert(wrapper.i === 6) }

    You can do the same thing with arguments:

    @@ -195,15 +203,15 @@ Or maybe you want to return a dictionary with one item being a wrapper. To do t
    }, {
    response: ReturnVal("contrivedObject")
    });

    front.reallyContrivedExample().then(obj => {
    assert(obj.a == "hello");
    assert(obj.b == "world");
    assert(wrapper.i == 1);
    assert(wrapperArray[0].i == 2);
    assert(wrapperArray[1].i == 3);
    });

    Actors
    ------

    @@ -215,12 +223,12 @@ Probably the most common objects that need custom martialing are actors themselv
    protocol.Actor.prototype.initialize.call(this, conn);
    },
    getID: method(function() {
    return this.id;
    return this.id;
    }, {
    response: { id: ReturnVal() },
    }
    });

    let ChildFront = protocol.FrontClass(ChildActor, {
    initialize: function(client, form) {
    protocol.Front.prototype.initialize.call(this, client, form);
    @@ -299,29 +307,29 @@ And imagine you want to change 'c' and return the object:
    // Oops! If a type is going to return references to itself or any other
    // type that isn't fully registered yet, you need to predeclare the type.
    types.addActorType("childActor");

    ...

    changeC: method(function(newC) {
    c = newC;
    return this;
    }, {
    request: { newC: Arg(0) },
    response: { self: ReturnVal("childActor") }
    });

    ...

    childFront.changeC('hello').then(ret => { assert(ret === childFront); assert(childFront.c === "hello") });

    Now our response will look like:

    { from: <childActorID>, self: { actor: <childActorID>, id: <id>, a: <a>, b: <b>, c: "hello", d: <d> }

    But that's wasteful. Only c changed. So we can provide a *detail* to the type using `#`:

    response: { self: ReturnVal("childActor#changec") }

    and update our form methods to make use of that data:

    ChildActor:
    @@ -331,7 +339,7 @@ and update our form methods to make use of that data:
    }
    ... // the rest of the form method stays the same.
    }

    ChildFront:
    form: function(form, detail) {
    if (detail === "changec") {
    @@ -341,9 +349,9 @@ and update our form methods to make use of that data:
    }
    ... // the rest of the form method stays the same.
    }

    Now the packet looks like a much more reasonable `{ from: <childActorID>, self: { actor: <childActorID>, c: "hello" } }`

    Lifetimes
    ---------

    @@ -363,7 +371,7 @@ Actors are subclasses of jetpack `EventTarget`, so you can just emit:
    }, {
    request: { news: Arg(0) }
    });

    ... but nobody will really care, because that's not going over the protocol. But you can describe the packet in an `events` member, the same way you would specify a request:

    events: {
    @@ -379,13 +387,13 @@ And now you can listen to events on a front:
    dump("Got good news: " + news + "\n");
    });
    front.giveGoodNews().then(() => { dump("request returned.") });

    You might want to update your front's state when an event is fired, before emitting it against the front. You can use `preEvent` in the front definition for that:

    countGoodNews: protocol.preEvent("good-news", function(news) {
    this.amountOfGoodNews++;
    });

    On a somewhat related note, not every method needs to be request/response. Just like an actor can emit a one-way event, a method can be marked as a one-way request. Maybe we don't care about giveGoodNews returning anything:

    giveGoodNews: method(function(news) {
    @@ -442,13 +450,13 @@ You can customize this behavior in two ways. The first is by defining a `defaul
    request: { id: Arg(0) },
    response: { child: ReturnVal("childActor") }
    });

    This creates a new child actor owned by the current child actor. But in this example we want all actors created by the child to be owned by the SimpleActor. So we can define a `defaultParent` property that makes use of the `parent` proeprty provided by the Actor class:

    get defaultParent() { return this.parent }

    The front needs to provide a matching `defaultParent` property that returns an owning front, to make sure the client and server lifetimes stay synced.

    For more complex situations, you can define your own lifetime properties. Take this new pair of SimpleActor methods:

    // When the "temp" lifetime is specified, look for the _temporaryParent attribute as the owner.
    @@ -485,7 +493,7 @@ This will require some matching work on the front:
    }, {
    impl: "_getTemporaryChild"
    }),

    clearTemporaryChildren: protocol.custom(function(id) {
    if (this._temporaryParent) {
    this._temporaryParent.destroy();
  29. Brandon Benvie revised this gist Apr 25, 2013. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion writing-actors.md
    Original file line number Diff line number Diff line change
    @@ -118,7 +118,7 @@ That's probably unnecessary nesting (if you're sure you won't be returning an ob

    and now your packet will look like:

    { from: <actorID>, a: <number>, b: number }
    { from: <actorID>, a: <number>, b: <number> }

    Types and Marshalling
    ---------------------
  30. Brandon Benvie revised this gist Apr 25, 2013. 1 changed file with 50 additions and 42 deletions.
    92 changes: 50 additions & 42 deletions writing-actors.md
    Original file line number Diff line number Diff line change
    @@ -10,14 +10,14 @@ So you have an idea for a Firefox developer tool. You (obviously) want it to wo
    SimpleBackend.prototype = {
    printHello: function() { dump("hello") }
    };

    and a frontend that's nicely tied in with the developer toolbox somehow. I don't know how, but it has access to a Target at least:

    function createBackend(target) {
    let backend = new SimpleBackend(target);
    backend.printHello();
    }

    I guess it's not the best idea for a developer tool.

    We want to make the backend into an actor. First we need to import the protocol library, and maybe destructure it a bit:
    @@ -32,7 +32,7 @@ Then we'll do a bit of magic to change backend into an actor. This uses the add
    initialize: function(conn) {
    protocol.Actor.prototype.initialize.call(this, conn); // the worst part of heritage.
    },

    printHello: function() { dump("hello") }
    });

    @@ -48,20 +48,20 @@ But fixing #2 should be easy. Replace `printHello` with:
    printHello: method(function() {
    dump("hello");
    })

    and now it supports a `printHello` request type that has request and response packets that look like:

    { to: <actorID>, type: 'printHello' }
    { from: <actorID> }

    So from the client side we need to create a *front* to interact with the actor. Here's a front for the SimpleBackend actor:

    let SimpleFront = protocol.FrontClass(SimpleBackend, {
    initialize: function(client, form) {
    protocol.Front.prototype.initialize.call(this, client, form);
    }
    });

    This front has the same problem as the actor, which is that I'm not telling you how to get the initial reference to the front. Sorry. But let's pretend you do have it, and want to say hello:

    let front = magicalGetSimpleFront();
    @@ -92,7 +92,7 @@ This will generate a request handler whose request and response packets looks li

    { to: <actorID>, type: "echo", echo: <str> }
    { from: <actorID>, echoed: <str> }

    The client usage should be predictable:

    front.echo("hello").then(str => { assert(str === "hello") })
    @@ -102,22 +102,22 @@ The library tries hard to make using fronts feel like natural javascript (or as
    Maybe your response is an object:

    addOneTwice: method(function(a, b) {
    return { a + 1, b + 1 };
    return { a: a + 1, b: b + 1 };
    }, {
    request: { a: Arg(0), b: Arg(1) }
    response: { ret: ReturnVal() }
    });

    This will generate a response packet that looks like:

    { from: <actorID>, ret: { a: <number>, b: <number> } }

    That's probably unnecessary nesting (if you're sure you won't be returning an object with 'from' as a key!), so you can just replace `response` with:

    response: ReturnVal()

    and now your packet will look like:

    { from: <actorID>, a: <number>, b: number }

    Types and Marshalling
    @@ -127,9 +127,16 @@ Things have been pretty simple up to this point - all the arguments we've passed

    Again, the protocol lib tries hard to provide a natural API to actors and clients, and sometime that natural API might involve object APIs. I'm going to use a wickedly contrived example, bear with me. Let's say I have a small object that contains a number and has a few methods associated with it:

    let NumberWrapper = function(i) { this.i }
    NumberWrapper.prototype = { increment: function() { i++ }, decrement: function() { i-- } };

    let NumberWrapper = function(i) {
    this.i = i;
    };

    NumberWrapper.prototype = {
    increment: function() { this.i++ },
    decrement: function() { this.i-- }
    };


    and I want to return it from a backend function:

    wrapNumber: method(function(i) {
    @@ -142,17 +149,18 @@ and I want to return it from a backend function:
    I want that response to look like `{ from: <actorID>, wrappedNumber: <number> }`, but the client side needs to know to return a NumberWrapper, not a primitive number. So let's tell the protocol lib about wrapped numbers:

    protocol.types.addType("numberWrapper", {
    write: (v) => { return v.i },
    read: (v) => { return new NumberWrapper(v) }
    write: (v) => v.i,
    read: (v) => new NumberWrapper(v)
    });

    and then change the response line to let the library know to use this wrapper:

    response: { wrappedNumber: ReturnVal("numberWrapper") }

    And now our client can use the API as expected:

    front.wrapNumber(5).then(wrapper => { wrapper.increment() assert(wrapper.i === 6) }
    front.wrapNumber(5).then(wrapper => {
    wrapper.increment() assert(wrapper.i === 6) }

    You can do the same thing with arguments:

    @@ -195,15 +203,15 @@ Or maybe you want to return a dictionary with one item being a wrapper. To do t
    }, {
    response: ReturnVal("contrivedObject")
    });

    front.reallyContrivedExample().then(obj => {
    assert(obj.a == "hello");
    assert(obj.b == "world");
    assert(wrapper.i == 1);
    assert(wrapperArray[0].i == 2);
    assert(wrapperArray[1].i == 3);
    });

    Actors
    ------

    @@ -215,12 +223,12 @@ Probably the most common objects that need custom martialing are actors themselv
    protocol.Actor.prototype.initialize.call(this, conn);
    },
    getID: method(function() {
    return this.id;
    return this.id;
    }, {
    response: { id: ReturnVal() },
    }
    });

    let ChildFront = protocol.FrontClass(ChildActor, {
    initialize: function(client, form) {
    protocol.Front.prototype.initialize.call(this, client, form);
    @@ -299,29 +307,29 @@ And imagine you want to change 'c' and return the object:
    // Oops! If a type is going to return references to itself or any other
    // type that isn't fully registered yet, you need to predeclare the type.
    types.addActorType("childActor");

    ...

    changeC: method(function(newC) {
    c = newC;
    return this;
    }, {
    request: { newC: Arg(0) },
    response: { self: ReturnVal("childActor") }
    });

    ...

    childFront.changeC('hello').then(ret => { assert(ret === childFront); assert(childFront.c === "hello") });

    Now our response will look like:

    { from: <childActorID>, self: { actor: <childActorID>, id: <id>, a: <a>, b: <b>, c: "hello", d: <d> }

    But that's wasteful. Only c changed. So we can provide a *detail* to the type using `#`:

    response: { self: ReturnVal("childActor#changec") }

    and update our form methods to make use of that data:

    ChildActor:
    @@ -331,7 +339,7 @@ and update our form methods to make use of that data:
    }
    ... // the rest of the form method stays the same.
    }

    ChildFront:
    form: function(form, detail) {
    if (detail === "changec") {
    @@ -341,9 +349,9 @@ and update our form methods to make use of that data:
    }
    ... // the rest of the form method stays the same.
    }

    Now the packet looks like a much more reasonable `{ from: <childActorID>, self: { actor: <childActorID>, c: "hello" } }`

    Lifetimes
    ---------

    @@ -363,7 +371,7 @@ Actors are subclasses of jetpack `EventTarget`, so you can just emit:
    }, {
    request: { news: Arg(0) }
    });

    ... but nobody will really care, because that's not going over the protocol. But you can describe the packet in an `events` member, the same way you would specify a request:

    events: {
    @@ -379,13 +387,13 @@ And now you can listen to events on a front:
    dump("Got good news: " + news + "\n");
    });
    front.giveGoodNews().then(() => { dump("request returned.") });

    You might want to update your front's state when an event is fired, before emitting it against the front. You can use `preEvent` in the front definition for that:

    countGoodNews: protocol.preEvent("good-news", function(news) {
    this.amountOfGoodNews++;
    });

    On a somewhat related note, not every method needs to be request/response. Just like an actor can emit a one-way event, a method can be marked as a one-way request. Maybe we don't care about giveGoodNews returning anything:

    giveGoodNews: method(function(news) {
    @@ -442,13 +450,13 @@ You can customize this behavior in two ways. The first is by defining a `defaul
    request: { id: Arg(0) },
    response: { child: ReturnVal("childActor") }
    });

    This creates a new child actor owned by the current child actor. But in this example we want all actors created by the child to be owned by the SimpleActor. So we can define a `defaultParent` property that makes use of the `parent` proeprty provided by the Actor class:

    get defaultParent() { return this.parent }

    The front needs to provide a matching `defaultParent` property that returns an owning front, to make sure the client and server lifetimes stay synced.

    For more complex situations, you can define your own lifetime properties. Take this new pair of SimpleActor methods:

    // When the "temp" lifetime is specified, look for the _temporaryParent attribute as the owner.
    @@ -485,7 +493,7 @@ This will require some matching work on the front:
    }, {
    impl: "_getTemporaryChild"
    }),

    clearTemporaryChildren: protocol.custom(function(id) {
    if (this._temporaryParent) {
    this._temporaryParent.destroy();