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), }, ) }