302 lines
6.5 KiB
Odin
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),
|
|
},
|
|
)
|
|
}
|