302 lines
6.5 KiB
Odin

package assetdsl
import "core:container/intrusive/list"
import "core:log"
import "core:mem"
import "core:mem/virtual"
import os "core:os/os2"
import "core:slice"
import "core:strings"
Asset_Builder :: struct {
arena: virtual.Arena,
// Allocator created from the arena above
allocator: mem.Allocator,
asset_defs: map[string]^Asset_Def,
gen_files: list.List,
}
Source_File_Node :: struct {
using node: Builder_Node,
path: string,
}
make_source_file_node :: proc(builder: ^Asset_Builder, path: string) -> (node: ^Source_File_Node) {
node = new(Source_File_Node, builder.allocator)
node.path = strings.clone(path, builder.allocator)
node.node.exec =
proc(ctx: ^Asset_Builder_Exec_Context, builder_node: ^Builder_Node) -> ([]byte, Error) {
source_file_node := container_of(builder_node, Source_File_Node, "node")
contents, err := os.read_entire_file(source_file_node.path, context.allocator)
if err != nil {
return nil, make_error(os.error_string(err))
}
return contents, nil
}
return
}
Source_File :: struct {
path: string,
}
Gen_File :: struct {
node: ^Gen_File_Node,
}
Lazy_Path :: union #no_nil {
Source_File,
Gen_File,
}
Asset_Builder_Exec_Context :: struct {}
Builder_Node :: struct {
needs_list: list.List,
needs_node: list.Node,
exec: proc(
ctx: ^Asset_Builder_Exec_Context,
builder_node: ^Builder_Node,
) -> (
[]byte,
Error,
),
}
Output_Node :: struct($T: typeid) {
using node: Builder_Node,
// Computed when this executes
value: Maybe(T),
}
Gen_File_Node :: struct {
using node: Builder_Node,
// List node to iterate all gen files. List itself is stored on builder
gen_files: list.Node,
// Absolute path filled when file is generated
abs_path: string,
}
make_output_node :: proc(builder: ^Asset_Builder, $T: typeid) -> ^Output_Node(T) {
context.allocator = builder.allocator
out_node := new(Output_Node(T))
return out_node
}
Run_Proc_Node :: struct {
using node: Builder_Node,
args: []Run_Arg,
}
Custom_Node :: struct($T: typeid) {
using node: Builder_Node,
data: T,
callback: proc(ctx: ^Asset_Builder_Exec_Context, data: ^T) -> ([]byte, Error),
}
make_custom_node :: proc(
builder: ^Asset_Builder,
data: string,
callback: proc(ctx: ^Asset_Builder_Exec_Context, data: ^string) -> ([]byte, Error),
) -> ^Custom_Node(string) {
node := new(Custom_Node(string), builder.allocator)
node.data = data
node.callback = callback
node.node.exec =
proc(ctx: ^Asset_Builder_Exec_Context, builder_node: ^Builder_Node) -> ([]byte, Error) {
custom_node := container_of(builder_node, Custom_Node(string), "node")
return custom_node.callback(ctx, &custom_node.data)
}
return node
}
add_dependency :: proc(node: ^Builder_Node, dependency: ^Builder_Node) {
list.push_back(&node.needs_list, &dependency.needs_node)
}
get_path_builder_node :: proc(path: Lazy_Path) -> (node: ^Builder_Node) {
switch p in path {
case Gen_File:
node = p.node
case Source_File:
}
return
}
make_run_proc_node :: proc(builder: ^Asset_Builder, args: []Run_Arg) -> ^Run_Proc_Node {
context.allocator = builder.allocator
run_node := new(Run_Proc_Node)
run_node.args = slice.clone(args)
// Add input dependencies
for &arg in run_node.args {
switch a in arg {
case string:
case ^Output_Node(string):
add_dependency(&run_node.node, &a.node)
case Arg_Path:
switch p in a {
case Input_Path:
if dep := get_path_builder_node(Lazy_Path(p)); dep != nil {
add_dependency(&run_node.node, dep)
}
case Output_Path:
}
}
}
run_node.exec = proc(ctx: ^Asset_Builder_Exec_Context, node: ^Builder_Node) -> Error {
run_node := container_of(node, Run_Proc_Node, "node")
resolved_args := make([]string, len(run_node.args))
for arg, i in run_node.args {
arg_str := resolve_arg(arg)
resolved_args[i] = arg_str
}
log.infof("run: {}", resolved_args)
return nil
}
stdout_node := make_output_node(builder, []byte)
stderr_node := make_output_node(builder, []byte)
add_dependency(&stdout_node.node, &run_node.node)
add_dependency(&stderr_node.node, &run_node.node)
return run_node
}
Lazy_Value :: union($T: typeid) #no_nil {
T,
^Output_Node(T),
}
Input_Path :: distinct Lazy_Path
Output_Path :: distinct Lazy_Path
Arg_Path :: union {
Input_Path,
Output_Path,
}
Run_Arg :: union #no_nil {
// Simple string
string,
Arg_Path,
// Will be evaluated at execution time
^Output_Node(string),
}
arg_in :: proc(path: Lazy_Path) -> Run_Arg {
return Arg_Path(Input_Path(path))
}
arg_out :: proc(path: Lazy_Path) -> Run_Arg {
return Arg_Path(Output_Path(path))
}
resolve_arg :: proc(arg: Run_Arg) -> string {
result: string
switch a in arg {
case string:
result = a
case Arg_Path:
result = "TODO"
case ^Output_Node(string):
result = a.value.?
}
return result
}
Run_Proc_Step :: struct {
stdout: ^Output_Node([]byte),
stderr: ^Output_Node([]byte),
}
Asset_Def :: struct {
id: string,
file: Gen_File,
}
Builder_Error :: struct {
msg: string,
}
Error :: union {
Builder_Error,
}
builder_init :: proc(builder: ^Asset_Builder, arena: virtual.Arena) {
builder.arena = arena
builder.allocator = virtual.arena_allocator(&builder.arena)
}
@(private = "file")
make_error :: proc(msg: string) -> Error {
return Builder_Error{msg = msg}
}
define_asset :: proc(builder: ^Asset_Builder, id: string) -> (^Asset_Def, Error) {
context.allocator = builder.allocator
asset_def, ok := builder.asset_defs[id]
if ok {
return nil, make_error("asset exists")
}
asset_def = new(Asset_Def)
asset_def.id = strings.clone(id)
asset_def.file = gen_file(builder)
builder.asset_defs[id] = asset_def
return asset_def, nil
}
source_file :: proc(builder: ^Asset_Builder, path: string) -> Lazy_Path {
return Source_File{path = strings.clone(path, builder.allocator)}
}
gen_file :: proc(builder: ^Asset_Builder) -> Gen_File {
gen_file_node := new(Gen_File_Node, builder.allocator)
list.push_back(&builder.gen_files, &gen_file_node.gen_files)
return Gen_File{node = gen_file_node}
}
run_proc :: proc(builder: ^Asset_Builder, args: []Run_Arg) -> (Run_Proc_Step, Error) {
}
export_model_from_blend :: proc(
builder: ^Asset_Builder,
blend: Lazy_Path,
output: Lazy_Path,
collection: string,
) {
}
define_assets :: proc(builder: ^Asset_Builder) {
ae86_asset := define_asset(builder, "models.ae86")
ae86_blend := source_file(builder, "ae86.blend")
ae86_convex_blend := source_file(builder, "car_convex.blend")
run_proc(
builder,
{
"blender",
"--python",
arg_in(source_file(builder, "export-model")),
arg_in(ae86_blend),
arg_out(ae86_asset.file),
},
)
}