-
Notifications
You must be signed in to change notification settings - Fork 1
Home
Adding a Lua plugin API
The Rez compiler uses a %Compilation{} struct that is passed between a series of phases that transform it. The first phase reads the source, subsequent phases built & manipulate an AST, the final phase writes output.
Rez has a @pragma directive that can trigger custom behaviours. For example @pragma write_hierarchy("hierarchy.ans") writes out a type hierarchy. This is built-in and implemented as an Elixir function.
The idea is to allow writing a custom @pragma as a Lua script that can modify the %Compilation{}.
defstruct status: :ok,
game: nil,
options: %{},
phase: nil,
source: nil,
source_path: nil,
path: nil,
dist_path: nil,
cache_path: nil,
content: [],
id_map: %{},
type_map: %{},
type_hierarchy: TypeHierarchy.new(),
defaults: %{},
aliases: %{},
constants: %{},
schema: nil,
pragmas: [],
progress: [],
errors: []
At certain points content becomes a list of nodes (e.g. %Rez.AST.Card{}, %Rez.AST.Actor{}, %Rez.AST.Inventory{}, ...) nodes.
So an example would be a pragma (e.g. @pragma annotate_images) to iterate all %Rez.AST.Asset{} content nodes and pre-processes the images to get them to conform to a certain format and annotate with extra metadata.
This would load "pragmas/annotate_images.lua" and pass the compilation into the Lua script, expecting a modified compilation to be returned.
Here's the current pragma.ex:
defmodule Rez.AST.Pragma do
@moduledoc """
Specifies the Pragma AST node.
"""
defstruct status: :ok,
game_element: false,
position: {nil, 0, 0},
name: "",
built_in: false,
values: [],
script: nil,
metadata: %{}
alias __MODULE__
alias Rez.Compiler.Compilation
@built_ins ["write_hierarchy"]
use Lua.API
deflua io_puts(s) do
IO.puts("Lua: #{inspect(s)}")
end
def build(name, values, position) when name in @built_ins do
{:ok,
%Pragma{
position: position,
name: name,
values: values,
built_in: true
}}
end
def build(name, values, position) do
with {:ok, lua_script} <- File.read("pragmas/#{name}.lua") do
{:ok,
%Pragma{
position: position,
name: name,
values: values,
script: lua_script
}}
end
end
def run(
%Pragma{built_in: true, name: "write_hierarchy", values: [file | _]},
%Compilation{type_hierarchy: type_hierarchy} = compilation
) do
case File.write(file, Apex.Format.format(type_hierarchy)) do
:ok ->
compilation
{:error, errno} ->
Compilation.add_error(
compilation,
"PRAGMA write_hierarchy: cannot write #{inspect(errno)}"
)
end
end
def run(%Pragma{name: name, values: values, script: script}, compilation) do
IO.puts("Run #{name}")
lua =
Lua.new()
|> Lua.load_api(__MODULE__)
{encoded, lua} = Lua.encode!(lua, {:userdata, compilation})
lua = Lua.set!(lua, [:compilation], encoded)
lua = Lua.set!(lua, [:values], values)
{result, lua} = Lua.eval!(lua, script)
result = Lua.decode!(lua, result)
# If the script returns the modified compilation, its decoded representation
# should
result
end
end
defimpl Rez.AST.Node, for: Rez.AST.Pragma do
def node_type(_pragma), do: "pragma"
def js_ctor(_pragma), do: raise("@pragma does not support a JS constructor!")
def js_initializer(_pragma), do: raise("@pragma does not support a JS initializer!")
def process(pragma, _resources), do: pragma
def html_processor(_pragma, _attr), do: raise("@pragma does not support HTML processing!")
end