Skip to content

Adding and using Substance plugins

Katherine Ye edited this page Mar 29, 2019 · 3 revisions

Introduction

This page defines the interface between the Penrose runtime and an external plugin that can instantiate a Substance program.

For Substance-writers: how to use a plugin

You can find the list of supported plugins in Plugins. To use one, add plugin "name" as one line at the top of Style. Penrose currently only supports using no plugins or 1 plugin.

For domain implementers: how to add a plugin

Currently plugins live in src/plugins, each in its own directory. Make a directory there and compile your plugin if needed.

Register the plugin in Plugins by adding an entry with the plugin name, the path to the plugin, and a command to run to invoke the plugin from that path. For example: ("ddgjs", ("plugins/mesh-plugin", "node mesh-plugin.js")) (here is the source for the mesh plugin). Recompile Penrose.

Plugins read from Sub-user.json in their own directory and write to Sub-instantiated.sub in their own directory.

Penrose sends the plugin a JSON representation of the Substance program via the file path above. Penrose expects a desugared Substance program back in the file path above.

Optionally, a plugin can output some values for a Style program to access. If a plugin named plugin outputs the file value.json following the schema [StyVal] defined in SubstanceJSON, then the values can be accessed in Style by the expression plugin[string1][string2], for example ddg[X.name]["x"]. All values must be floating-point values for now.

How Penrose handles plugins

The Substance JSON schema is defined as the Haskell datatype SubSchema in SubstanceJSON.

Penrose will append the desugared Substance program to the original Substance program and parse + check the whole thing again.

Plugin design pattern

Instantiate all objects, then make sure the objects satisfy the constraints, keeping some state along the way. For an example plugin and its approach, see plugins/mesh-plugin/mesh-plugin.js.

Assumptions

Most importantly, plugin writers are responsible for respecting the Substance specification.

For example, if a Substance writer writes

Graph G
Graph C := CycleIn(G)

Then plugins that instantiate the graph G with a concrete data structure are responsible for making sure that G has a cycle and that the cycle is named C.

Other assumptions and conventions:

  • JSON order may not correspond to Substance program order, so treat it as unordered
  • Plugin writer will refer to the Element program for the names and types
  • Plugin writer never has to deal with nested expressions (Substance compiler handles naming intermediate expressions)
  • Plugin writer gets the names of bound variables (such as v in v := f(a, b))

Example of what's sent to a plugin

Substance program:

Set A
Set B
IsSubset(A,B)

Set C
C := Union(A, B)

Point p
PointIn(C, p)
C := AddPoint(p, C)

AutoLabel All

Substance AST:

[ Decl (TConstr "Set" []) (VarConst "A")
, Decl (TConstr "Set" []) (VarConst "B")
, ApplyP
    PredicateConst
    "IsSubset"
    [ PE (VarE (VarConst "A")) , PE (VarE (VarConst "B")) ]
, Decl (TConstr "Set" []) (VarConst "C")
, Bind
    (VarConst "C")
    (ApplyFunc "Union" [ VarE (VarConst "A") , VarE (VarConst "B") ])
, Decl (TConstr "Point" []) (VarConst "p")
, ApplyP
    PredicateConst
    "PointIn"
    [ PE (VarE (VarConst "C")) , PE (VarE (VarConst "p")) ]
, Bind
    (VarConst "C")
    (ApplyFunc
       "AddPoint" [ VarE (VarConst "p") , VarE (VarConst "C") ])
, AutoLabel Default
]

JSON schema that's sent to plugin: (TODO: add Substance values and their types to schema)

{
  "constraints": {
    "predicates": [
      {
        "pargNames": [
          "C",
          "p"
        ],
        "pname": "PointIn"
      },
      {
        "pargNames": [
          "A",
          "B"
        ],
        "pname": "IsSubset"
      }
    ],
    "functions": [
      {
        "fargNames": [
          "p",
          "C"
        ],
        "fname": "AddPoint",
        "varName": "C"
      },
      {
        "fargNames": [
          "A",
          "B"
        ],
        "fname": "Union",
        "varName": "C"
      }
    ]
  },
  "objects": [
    {
      "objName": "p",
      "objType": "Point"
    },
    {
      "objName": "C",
      "objType": "Set"
    },
    {
      "objName": "B",
      "objType": "Set"
    },
    {
      "objName": "A",
      "objType": "Set"
    }
  ]
}

Example of values.json output for Style: (this is from the mesh plugin)

[
  {
    "nameVals": [
      {
        "propertyVal": -0.030517175058609514,
        "propertyName": "x"
      },
      {
        "propertyVal": 0.22126884822375148,
        "propertyName": "y"
      }
    ],
    "subName": "K2_V3"
  },
  {
    "nameVals": [
      {
        "propertyVal": -0.6160663027512623,
        "propertyName": "x"
      },
      {
        "propertyVal": 0.10231779964600873,
        "propertyName": "y"
      }
    ],
    "subName": "K2_V4"
  },
  {
    "nameVals": [
      {
        "propertyVal": 0.39444688949943707,
        "propertyName": "x"
      },
      {
        "propertyVal": -0.21759831048711886,
        "propertyName": "y"
      }
    ],
    "subName": "j"
  },
  ...
]

Questions

  • What to do about subtyping?
  • How to convert Substance forms that are currently not handles? (e.g. binding a variable, deconstructor, predicate inequality)
  • Converting parametrized types?
  • Eventually we want to support Substance programs that use multiple Element programs. How does that interact w/ using multiple plugins?
Clone this wiki locally