modgo specification - modrpc/info GitHub Wiki

ModGo programs are syntactically-legal Go programs annotated with ModGo directives. A ModGo directive is provided inside Go comments and the ModGo compiler transforms ModGo programs into a legal Go program.

Modules

A ModGo module is a basic unit of deployment. It is technically a Go package file with ModGo directives. Later, a ModGo module can be instantiated in nodes -- note that "node" is a run-time notion which will be discussed later. To specify that a Go package is a module, add the directive //modgo:module in a comment in the line right before the package declaration.

  //modgo:module
  package counter

When a directory which contains the ModGo module consists of multiple files, only those files which contains //modgo:module will be considered as ModGo code and will be transformed by the ModGo compiler.

Module Parameters

A module parameter represents a variable which can be set during the instantiation of the module.

  //modgo:param
  var Width int

Properties

A property is a variable which can be set/reset (remotely) through ModGo built-in functions MG_Get and MG_Set.

  //modgo:prop
  var Counter int 

For example, let a node with the url "tcp://192.168.8.109" maintains a property named Temperature. Then, its value can be read inside the ModGo module, using the MG_Get function call as shown below:

  t, err := MG_Get("tcp://192.168.8.109", "Temperature")

Events

An event is a variable which can be published by calling MG_Publish.
Only the code within the same module which defines the event can publish the given event. When an event is published (i.e. an event occurrence was generated), all reactors which have subscribed this event will be executed.

  //modgo:event
  var BatteryLow int 

When a ModGo event is defined, users can wait for such event using MG_Wait.

  LedNodeUrl := "tcp://192.168.8.109"
  b, error := MG_Wait(LedNodeUrl, "BatteryLow")
  fmt.Printf("Battery is low (%d) at %s\n", b, LedNodeUrl)

Timers

  //modgo:timer
  var MasterClock time.Duration = 100 * time.Milisecond
  MG_Tick(MasterClock)
  fmt.Println("MasterClock Ticks")

External Function Declarations

  //modgo:extern nodeurl foo(arg0 int, arg1 string) (ret1 int, ret2 error)

Later inside the body of the code in this file, one can call foo.

Functions

A ModGo function is one which can be called from outside -- e.g. remote nodes.

  //modgo:func
  func Add(n0 int, n1 int) int {
    return n0 + n1
  }

The name of the function must be in a capital letter (i.e. exported Go function). One important point about ModGo functions are that they can contain calls to remote ModGo functions using the builtin function MG_Call. This means that while serving a remote request to the given function, it can, in turn, make a remote call to functions in other nodes. This effectively, creates a call chain across multiple nodes facilitating the complex interaction of nodes.

Processes

A ModGo process is a free-running piece of code which repeats itself.

  //modgo:process
  func BatteryLowWatcher() {
    b, error := MG_Wait("tcp://192.168.8.109", "BatteryLow")
    fmt.Printf("Battery is low (%d)\n", b)
  }

In general, a ModGo process should contain at least one function call to MG_Wait or MG_Tick functions, so that the body of the process will be executed only when some events occur.

Below is an example, which represents a sequential process which which periodically grabs a value from another node and appends to the local node.

  //modgo:process
  func WatchDog() {
    MG_Tick(MasterClock)
    value, err := MG_Call("tcp://192.168.8.109", "GetValue")
    if (err != nil) {
      if (err == MG_ErrNetFail) {
       // do some action
      }
    } else {
      values = append(values, value)
    }
  }

Reactors

A reactor is a function with a single argument of interface{} type, with //mg:reactor directive. The directive must include which event resource this reactor will respond to. The reactor will be executed once for every occurrence of given event.

  func EventHandler(ev interface{}) { // mg:reactor ev={namespace,name}
    log.Println("Event occurred")
  }

In the event specification, ev={namespace, name}, namespace and name can be constant strings or variables.

Naming Resources

When a module (along with its resources) is defined, and deployed to a node, the resources become available for use by other nodes. To use a resource, there must exist ways to refer to it.

There are two ways to designate a resource in ModGo. The first option is to use an absolute URL of the resource, which consists of the address of the network interface (e.g. tcp://192.168.8.109) and the resource name (e.g. Counter). However, this scheme has a problem that the network address of the node is hard-coded.

Another option is to use a pair of two names: namespace and resource. Namespace is a collection of names of resources and (sub-)namespaces. A namespace is maintained by a single node and any resource is associated with at least one a single namespace. (A natural, mandatory namespace associated each resource is the network address in which the module containing the resource was deployed is connected to).

In examples presented so far, all resource accesses use absolute node address as the namespace. However, for flexible resource access in mobile environments, we need to bind the namespace to node dynamically. See below for example:

  //modgo:module
  package LedLight

There are several builtin namespaces that users can use in the first argument of functions such as MG_Call.

  • ".": The namespace in which this module is sitting in.
  • MG_Parent: Parent namespace
  • MG_SelfNode: Parent namespace

Namespace

A ModGo resource name always exists relative to a namespace. A resource, when instantiated in node, has a name which is contained in the namespace owned by the node -- the resource name must be unique in its namespace and renaming may be involved in case.

A single ModGo namespace, a set of resource names, is managed by a node. A node, say n0, automatically forms a namespace of names of resources contained in modules instantiated within the node.

A ModGo namespaces are hierarchical but the hierarchy is formed form dynamically through node connections. When a node, say n1 joins the network with n0 as its parent, a parent-child relationship is established, extending the naming hierarchy. Node n1 also has its namespace and thus,

This dynamic nature of namespaces give flexibility to handle dynamic node connection topology in mobile environment. For example, suppose that we want to define a new behavior that when a node (which contains instantiation of module M) is connected to a network. It wants to call the parent node's service X.

   package SubModule  // mg:module

   func OnboardHandler(ev MG_event) { // mg:reactor ev=_SysEv_Connected
     modinstname := MG_get(".", "GetInstanceName")
     MG_call("..", "RegisterChild", modinstname)
   }

The syntax of a namespace is given below:

  • . refers to the namespace of the node which contains instance of this module
  • .. refers to the namespace of the parent node of the node which contains instance of this module

Resource name

Built-in ModGo functions

MG_get

  func MG_get(namespace string, name string) (interface{}, error)

The target resource (property) is denoted by the combination of namespace and name. The value returned by MG_get has interface type so it's user's responsibility to check the type of return value e.g. through Go type assertion/casting.

Note that both namespace and resource name are just strings. This means that it's possible to dynamically change the values and create code which depends on the value of the namespace or resource name.

  package MyModule // modgo:module
  var DestNS string   // modgo:prop
  var DestName string // modgo:prop
  func MyReactor(ev MG_event) { // modgo:event ev=xxx
    x, err := MODG_get(DestNS, DestName)
    switch x.(type) {
    }
  }

The errors raised by this function are following:

   ErrInvalidNamespace
   ErrResourceNotFound

MG_set

  func MG_set(namespace string, name string, value interface{}) (error)

MG_call

  func MG_set(namespace string, name string, args...interface{}) (interface{}, error)

MG_cancel

Can we do this?? Or should cancellation be a concept hidden to user-code?

  func MG_set(namespace string, name string, args...interface{}) (interface{}, error)

MG_publish

  func MG_publish(namespace string, name string) (error)

MG_wait

  func MG_wait(namespace string, name string, args... interface{}) (error)

The given resource referred to by name in the given namespace must be a ModGo event -- i.e. in the corresponding module definition, the given resource should have been annotated with // modgo:event.

MG_parent

MG_children

Built-in Resources -- Properties, Events, and Functions

Built-in properties

Built-in events

  _EvRuntimeInit
  _EvRuntimeSaved
  _EvRuntimeLoaded
  -EvRuntimeModDeployed
  -EvRuntimeModRemoved

Built-in functions

Module Instantiation

Namespace

ModGo Names

Before we delve into the details, let's think about what "names" mean. When we talk about a name in a programming language like C++, a name can refer to C++ artifacts such as variables or functions. Given that a (static) program is just a shadow of what is coming (actual execution), a textual name eventually refer to some dynamic entity inside computer memory.

can refer to some Actually, given that a (static) program is just a means to achieve some (run-time/dynamic) goal of execution. Each variable or function denotes (refers to) something during run-time. The extent of a name does not go beyond the C++ program itself. Sometimes, a name goes beyond that (i.e. to the meta-level) -- such as metalinguistic features through relfection.

===========

OLD

===========

// port definition port { instid int m0 mod:Z/W }

// exported services -- capitalized vars, channels, and functions var ExportedVar int var localVar int chan BatteryLowEvent int func ExportedFunc() int { fmt.Println("ExportedFunc called") return m0.Foo(localVar) + 1 }


## Module Imports
A module can instantiate other modules. For this, it must import the modules first. To distinguish from Go package imports, the module path is prefixed with "mod:".

## Port Definition
A module definition can contain an optional port definition. Ports are like arguments to module instantiations.

## Timer Definition

## Event Definition

## Hook Definition
Users can define some special functions which will be called on special occasions, e.g. when the module is deployed onto a device.
* <code>OnInit</code>
* <code>OnSave</code>

## Process Definition
In addition to (passive) exported functions which can be called from other modules, a module can contain processes. A process is one of:
* **initial process**, which is executed whenever the module is activated.
* **reactive process**, which is executed whenever the events it waits on occurs.

```go
proc Init() {
  localVar = initval
}
proc BaatteryLowReactor(lowev Y.BatteryLowEvent) {
  fmt.Println("Battery is low (%d percent) at device: %s", lowev, MVGO_DeviceName(Y)) 
}

Module instantiation

A module can be instantiated either statically or dynamically.

var m0 DisplayModule{}
var m1 *DisplayModule
func GetDisplayModule() {
  if (m1 == nil) {
    m1 := new(DisplayModule){}
  }
}

Remote Service Requests

Configuration Definition

A configuration consists of module instantiations.

config C {
   // <module name> <instance name>(pipe_port_connection)
   modinst m2;  // forward declaration
   m0 M("tcp:192.168.8.101:1234"){0, m2}
   M m1("tcp:192.168.8.102:1234"){1, m0}
   M m2("tcp:192.168.8.102:1234"){2, m1}
}
⚠️ **GitHub.com Fallback** ⚠️