csyonghe / Spire

Other
174 stars 22 forks source link

Interfaces or "signatures" for modules #19

Open tangent-vector-personal opened 7 years ago

tangent-vector-personal commented 7 years ago

If I am defining a module that needs access to component that will be provided by some other module (perhaps one of several possible implementations), the current Spire compiler forces me to require every individual component. This is tedious, and also seems like it will give a bad user experience when adding/removing/renaming components that are shared in this way.

Ideally, I'd like to be able to declare an interface or signature that lists a set of related components:

interface TangentFrame
{
    // names in an `interface` act as if declared `public` in a module
    vec3 normal;
    vec3 tangentU;
    vec3 tangentV;
}

and then in a module that wants to depend on these components, I do so through a require:

module Lighting
{
    // require some implementation of the interface, and import its components into explicit namespace
    require tangentFrame : TangentFrame;

    // implicit namespace variation also allowed, as for `using`
    // require TangentFrame;

    vec3 nDotL = dot(tangentFrame.normal, ...);
}

A module can declare that it implements the interface using ordinary object-oriented syntax:

module NormalMapping : TangentFrame
{
    vec3 normal = ...;
    // ...
}

In the simple case, a module that implements an interface will need to declare components with matching names/types (and for simplicity one might require that they be public, but that shouldn't really matter). More explicit syntax could be adopted if the object-oriented case doesn't feel right.

When using a module like Lighting above, the interface requirement can be filled in just as for an ordinary component require; the only difference is that it gets filled in with a module instead of a component.

shader Simple
{
     // completely implicit case:
    using NormalMapping; // use a module that implements interface
    using Lighting; // module that requires interface automatically gets it as input

    // completely explicit case:
    using nm = NormalMapping(...);
    using lighting = Lighting(tangentFrame: nm);
}

The implicit case should probably be limited so that we can only implicitly fill in a required module when there is exactly one module in the current scope that implements it.

It should be clear that everything above would extend to having a require on module "classes" rather than just interfaces, and it would also seem to generalize to supporting function or module members in an interface. Those generalizations aren't part of the initial feature request, though.