A bunch of prototype code for asset pipeline
This commit is contained in:
parent
a8e3bb6104
commit
5f4fa12040
352
assetcomp/assetcomp.odin
Normal file
352
assetcomp/assetcomp.odin
Normal file
@ -0,0 +1,352 @@
|
|||||||
|
package assetcomp
|
||||||
|
|
||||||
|
import "common:encoding/sexp"
|
||||||
|
import "common:name"
|
||||||
|
import "core:container/intrusive/list"
|
||||||
|
import "core:fmt"
|
||||||
|
import "core:log"
|
||||||
|
import os "core:os/os2"
|
||||||
|
|
||||||
|
_ :: sexp
|
||||||
|
|
||||||
|
main :: proc() {
|
||||||
|
context.logger = log.create_console_logger()
|
||||||
|
file, file_err := os.read_entire_file("src_assets/defs.ass", context.temp_allocator)
|
||||||
|
|
||||||
|
if file_err != nil {
|
||||||
|
log.fatalf("failed to read ass file {}", file_err)
|
||||||
|
}
|
||||||
|
|
||||||
|
parser: sexp.SEXP_Parser
|
||||||
|
parser.data = transmute(string)file
|
||||||
|
expr_list, err := sexp.parse(&parser)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
w := os.to_writer(os.stderr)
|
||||||
|
sexp.print_pretty_error(w, parser.data, err)
|
||||||
|
fmt.print("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
it := sexp.iterator_list(expr_list)
|
||||||
|
|
||||||
|
for expr in sexp.iterator_next(&it) {
|
||||||
|
sexp.print_sexp(expr.expr, os.to_writer(os.stdout))
|
||||||
|
fmt.print("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
asset_gather_ctx: Asset_Gather_Context
|
||||||
|
|
||||||
|
interpreter: Lisp_Interpreter
|
||||||
|
interpreter.user_data = &asset_gather_ctx
|
||||||
|
interpreter.external_func_table[sexp.Ident(name.from_string("define-asset"))] =
|
||||||
|
lisp_define_asset
|
||||||
|
interpreter.external_func_table[sexp.Ident(name.from_string("run-proc"))] = lisp_run_proc
|
||||||
|
|
||||||
|
lisp_interpret_expr_list(&interpreter, sexp.iterator_list(expr_list))
|
||||||
|
|
||||||
|
for n, local_func in interpreter.local_func_table {
|
||||||
|
log.debugf("local func {}\nargs: ", name.to_string(name.Name(n)))
|
||||||
|
sexp.print_list(local_func.args, os.to_writer(os.stderr))
|
||||||
|
log.debugf("\nbody: ")
|
||||||
|
sexp.print_list(local_func.body, os.to_writer(os.stderr))
|
||||||
|
log.debugf("\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lisp_run_proc :: proc(ctx: ^Lisp_Interpreter, args: sexp.List_Iterator) -> sexp.Sexp {
|
||||||
|
log.debugf("called run-proc with args:")
|
||||||
|
args := args
|
||||||
|
for body in sexp.iterator_next(&args) {
|
||||||
|
sexp.print_sexp(lisp_interpret_expr(ctx, body.expr), os.to_writer(os.stdout))
|
||||||
|
fmt.print("\n")
|
||||||
|
}
|
||||||
|
fmt.print("\n")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
lisp_define_asset :: proc(ctx: ^Lisp_Interpreter, args: sexp.List_Iterator) -> sexp.Sexp {
|
||||||
|
// asset_ctx := cast(^Asset_Gather_Context)ctx.user_data
|
||||||
|
|
||||||
|
args := args
|
||||||
|
type := name.Name(
|
||||||
|
lisp_interpret_expr(ctx, sexp.iterator_next_checked(&args)).(sexp.Atom).(sexp.Tag),
|
||||||
|
)
|
||||||
|
id := lisp_interpret_expr(ctx, sexp.iterator_next_checked(&args)).(sexp.Atom).(string)
|
||||||
|
|
||||||
|
log.debugf("called define-asset with id {}, type {}, body:", id, name.to_string(type))
|
||||||
|
for body in sexp.iterator_next(&args) {
|
||||||
|
sexp.print_sexp(lisp_interpret_expr(ctx, body.expr), os.to_writer(os.stdout))
|
||||||
|
fmt.print("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
Lisp_External_Func :: #type proc(ctx: ^Lisp_Interpreter, args: sexp.List_Iterator) -> sexp.Sexp
|
||||||
|
|
||||||
|
global_func_table: map[sexp.Ident]Lisp_External_Func
|
||||||
|
|
||||||
|
@(init)
|
||||||
|
init_global_funcs :: proc() {
|
||||||
|
global_func_table[sexp.Ident(name.from_string("defn"))] =
|
||||||
|
proc(ctx: ^Lisp_Interpreter, args: sexp.List_Iterator) -> sexp.Sexp {
|
||||||
|
args := args
|
||||||
|
name := sexp.to_ident_checked(sexp.iterator_next_checked(&args))
|
||||||
|
func_args := sexp.iterator_next_checked(&args).(sexp.Sexp_List)
|
||||||
|
body := args
|
||||||
|
|
||||||
|
ctx.local_func_table[name] = Local_Func {
|
||||||
|
args = sexp.iterator_list(func_args),
|
||||||
|
body = body,
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
global_func_table[sexp.Ident(name.from_string("def"))] =
|
||||||
|
proc(ctx: ^Lisp_Interpreter, args: sexp.List_Iterator) -> sexp.Sexp {
|
||||||
|
args := args
|
||||||
|
name := sexp.to_ident_checked(sexp.iterator_next_checked(&args))
|
||||||
|
value := sexp.iterator_next_checked(&args)
|
||||||
|
|
||||||
|
result := lisp_interpret_expr(ctx, value)
|
||||||
|
get_scope(ctx).values[name] = result
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
global_func_table[sexp.Ident(name.from_string("let"))] =
|
||||||
|
proc(ctx: ^Lisp_Interpreter, args: sexp.List_Iterator) -> sexp.Sexp {
|
||||||
|
args := args
|
||||||
|
bindings := sexp.iterator_next_checked(&args).(sexp.Sexp_List)
|
||||||
|
|
||||||
|
push_scope(ctx)
|
||||||
|
defer pop_scope(ctx)
|
||||||
|
scope := get_scope(ctx)
|
||||||
|
|
||||||
|
binds_it := sexp.iterator_list(bindings)
|
||||||
|
for name_expr in sexp.iterator_next(&binds_it) {
|
||||||
|
var_name := sexp.to_ident_checked(name_expr.expr)
|
||||||
|
value := lisp_interpret_expr(ctx, sexp.iterator_next_checked(&binds_it))
|
||||||
|
scope.values[var_name] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
body := args
|
||||||
|
|
||||||
|
return lisp_interpret_expr_list(ctx, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
global_func_table[sexp.Ident(name.from_string("temp-dir"))] =
|
||||||
|
proc(ctx: ^Lisp_Interpreter, args: sexp.List_Iterator) -> sexp.Sexp {
|
||||||
|
args := args
|
||||||
|
pattern := lisp_interpret_expr(ctx, sexp.iterator_next_checked(&args)).(sexp.Atom).(string)
|
||||||
|
assert(len(pattern) > 0)
|
||||||
|
|
||||||
|
temp_dir, _ := os.temp_dir(context.temp_allocator)
|
||||||
|
temp_path, _ := os.mkdir_temp(temp_dir, pattern, context.temp_allocator)
|
||||||
|
|
||||||
|
return sexp.Atom(temp_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
global_func_table[sexp.Ident(name.from_string("if"))] =
|
||||||
|
proc(ctx: ^Lisp_Interpreter, args: sexp.List_Iterator) -> sexp.Sexp {
|
||||||
|
args := args
|
||||||
|
condition := lisp_interpret_expr(ctx, sexp.iterator_next_checked(&args))
|
||||||
|
|
||||||
|
true_body := sexp.iterator_next_checked(&args)
|
||||||
|
|
||||||
|
if condition != nil {
|
||||||
|
return lisp_interpret_expr(ctx, true_body)
|
||||||
|
} else {
|
||||||
|
false_body, ok := sexp.iterator_next(&args)
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
return lisp_interpret_expr(ctx, false_body.expr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Local_Func :: struct {
|
||||||
|
args: sexp.List_Iterator,
|
||||||
|
body: sexp.List_Iterator,
|
||||||
|
}
|
||||||
|
|
||||||
|
Scope :: struct {
|
||||||
|
values: map[sexp.Ident]sexp.Sexp,
|
||||||
|
}
|
||||||
|
|
||||||
|
MAX_SCOPE_DEPTH :: 32
|
||||||
|
|
||||||
|
Lisp_Interpreter :: struct {
|
||||||
|
user_data: rawptr,
|
||||||
|
external_func_table: map[sexp.Ident]Lisp_External_Func,
|
||||||
|
local_func_table: map[sexp.Ident]Local_Func,
|
||||||
|
scope_stack: [MAX_SCOPE_DEPTH]Scope,
|
||||||
|
scope_idx: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
push_scope :: proc(ctx: ^Lisp_Interpreter) {
|
||||||
|
assert(ctx.scope_idx + 1 < len(ctx.scope_stack))
|
||||||
|
|
||||||
|
ctx.scope_idx += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
pop_scope :: proc(ctx: ^Lisp_Interpreter) {
|
||||||
|
assert(ctx.scope_idx - 1 >= 0)
|
||||||
|
|
||||||
|
delete(ctx.scope_stack[ctx.scope_idx].values)
|
||||||
|
ctx.scope_idx -= 1
|
||||||
|
}
|
||||||
|
|
||||||
|
get_scope :: proc(ctx: ^Lisp_Interpreter) -> ^Scope {
|
||||||
|
return &ctx.scope_stack[ctx.scope_idx]
|
||||||
|
}
|
||||||
|
|
||||||
|
find_value_scope :: proc(ctx: ^Lisp_Interpreter, name: sexp.Ident) -> sexp.Sexp {
|
||||||
|
idx := ctx.scope_idx
|
||||||
|
for idx >= 0 {
|
||||||
|
scope := &ctx.scope_stack[idx]
|
||||||
|
|
||||||
|
value, ok := scope.values[name]
|
||||||
|
if ok {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
idx -= 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
lisp_apply :: proc(
|
||||||
|
ctx: ^Lisp_Interpreter,
|
||||||
|
func: sexp.Ident,
|
||||||
|
args: sexp.List_Iterator,
|
||||||
|
) -> (
|
||||||
|
result: sexp.Sexp,
|
||||||
|
) {
|
||||||
|
log.debugf("apply {} {}", name.to_string(name.Name(func)), sexp.to_string_temp(args))
|
||||||
|
{
|
||||||
|
local_func, ok := ctx.local_func_table[func]
|
||||||
|
if ok {
|
||||||
|
push_scope(ctx)
|
||||||
|
defer pop_scope(ctx)
|
||||||
|
|
||||||
|
scope := get_scope(ctx)
|
||||||
|
|
||||||
|
def_args_it := local_func.args
|
||||||
|
provided_args_it := args
|
||||||
|
for defined_arg in sexp.iterator_next(&def_args_it) {
|
||||||
|
var_name := sexp.to_ident_checked(defined_arg.expr)
|
||||||
|
var_value := lisp_interpret_expr(
|
||||||
|
ctx,
|
||||||
|
sexp.iterator_next_checked(&provided_args_it),
|
||||||
|
)
|
||||||
|
|
||||||
|
log.debugf(
|
||||||
|
"adding local var to scope {} {}",
|
||||||
|
name.to_string(name.Name(var_name)),
|
||||||
|
sexp.to_string_temp(var_value),
|
||||||
|
)
|
||||||
|
|
||||||
|
scope.values[var_name] = lisp_interpret_expr(ctx, var_value)
|
||||||
|
}
|
||||||
|
|
||||||
|
body_it := local_func.body
|
||||||
|
for body in sexp.iterator_next(&body_it) {
|
||||||
|
// Last one will be the actual result
|
||||||
|
result = lisp_interpret_expr(ctx, body.expr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
external_func, ok := ctx.external_func_table[func]
|
||||||
|
if ok {
|
||||||
|
return external_func(ctx, args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
global_func, ok := global_func_table[func]
|
||||||
|
if ok {
|
||||||
|
return global_func(ctx, args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.fatalf("function with name {} not found", name.to_string(name.Name(func)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
lisp_interpret_expr_list :: proc(
|
||||||
|
ctx: ^Lisp_Interpreter,
|
||||||
|
body: sexp.List_Iterator,
|
||||||
|
) -> (
|
||||||
|
result: sexp.Sexp,
|
||||||
|
) {
|
||||||
|
body := body
|
||||||
|
for expr in sexp.iterator_next(&body) {
|
||||||
|
result = lisp_interpret_expr(ctx, expr.expr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
lisp_interpret_expr :: proc(ctx: ^Lisp_Interpreter, expr: sexp.Sexp) -> (result: sexp.Sexp) {
|
||||||
|
for {
|
||||||
|
switch &s in expr {
|
||||||
|
case sexp.Atom:
|
||||||
|
#partial switch a in s {
|
||||||
|
case sexp.Ident:
|
||||||
|
result = find_value_scope(ctx, a)
|
||||||
|
case:
|
||||||
|
result = s
|
||||||
|
}
|
||||||
|
case sexp.Sexp_List:
|
||||||
|
if list.is_empty(cast(^list.List)&s) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
it := sexp.iterator_list(s)
|
||||||
|
raw_func_expr := sexp.iterator_next_checked(&it)
|
||||||
|
func_ident, ok := sexp.to_ident(raw_func_expr)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
func_expr := lisp_interpret_expr(ctx, raw_func_expr)
|
||||||
|
func_ident, ok = sexp.to_ident(func_expr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
log.fatalf("expected identifier in a call expression, got {}", func_ident)
|
||||||
|
}
|
||||||
|
|
||||||
|
result = lisp_apply(ctx, func_ident, it)
|
||||||
|
}
|
||||||
|
|
||||||
|
if result == nil || !sexp.is_ident_expr(result) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Asset_Model :: struct {
|
||||||
|
// Expression that defines a model
|
||||||
|
model: sexp.Sexp,
|
||||||
|
}
|
||||||
|
|
||||||
|
Asset_Union :: union {
|
||||||
|
Asset_Model,
|
||||||
|
}
|
||||||
|
|
||||||
|
Asset_Def :: struct {
|
||||||
|
data: Asset_Union,
|
||||||
|
}
|
||||||
|
|
||||||
|
Asset_Gather_Context :: struct {
|
||||||
|
assets: map[string]Asset_Def,
|
||||||
|
}
|
@ -14,6 +14,7 @@ Build_Variant :: enum {
|
|||||||
Hot_Reload,
|
Hot_Reload,
|
||||||
Desktop,
|
Desktop,
|
||||||
Web,
|
Web,
|
||||||
|
Assetcomp,
|
||||||
}
|
}
|
||||||
|
|
||||||
Options :: struct {
|
Options :: struct {
|
||||||
@ -282,6 +283,7 @@ main :: proc() {
|
|||||||
"odin",
|
"odin",
|
||||||
"build",
|
"build",
|
||||||
"game",
|
"game",
|
||||||
|
"-define:NAME_STATIC_INIT=false",
|
||||||
"-define:RAYLIB_SHARED=true",
|
"-define:RAYLIB_SHARED=true",
|
||||||
"-define:PHYSFS_SHARED=true",
|
"-define:PHYSFS_SHARED=true",
|
||||||
"-define:DEV=true",
|
"-define:DEV=true",
|
||||||
@ -371,5 +373,20 @@ main :: proc() {
|
|||||||
),
|
),
|
||||||
"./bin/web/odin.js",
|
"./bin/web/odin.js",
|
||||||
)
|
)
|
||||||
|
case .Assetcomp:
|
||||||
|
remove_all("./bin/assetcomp")
|
||||||
|
mkdir_all("./bin/assetcomp")
|
||||||
|
|
||||||
|
cmd := slice.concatenate(
|
||||||
|
[][]string {
|
||||||
|
[]string{"odin", "build", "assetcomp", "-out:./bin/assetcomp/assetcomp" + EXE_EXT},
|
||||||
|
// tracy_flag,
|
||||||
|
debug_flag,
|
||||||
|
optimize_flag,
|
||||||
|
COMMON_FLAGS,
|
||||||
|
},
|
||||||
|
context.temp_allocator,
|
||||||
|
)
|
||||||
|
run_cmd(cmd, "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
301
common/assetdsl/assetdsl.odin
Normal file
301
common/assetdsl/assetdsl.odin
Normal file
@ -0,0 +1,301 @@
|
|||||||
|
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),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
451
common/encoding/sexp/sexp.odin
Normal file
451
common/encoding/sexp/sexp.odin
Normal file
@ -0,0 +1,451 @@
|
|||||||
|
package sexp
|
||||||
|
|
||||||
|
import "common:name"
|
||||||
|
import "core:container/intrusive/list"
|
||||||
|
import "core:fmt"
|
||||||
|
import "core:io"
|
||||||
|
import "core:strings"
|
||||||
|
import "core:testing"
|
||||||
|
import "core:unicode"
|
||||||
|
import "core:unicode/utf8"
|
||||||
|
|
||||||
|
SEXP_Parser :: struct {
|
||||||
|
data: string,
|
||||||
|
pos: int,
|
||||||
|
c: rune,
|
||||||
|
}
|
||||||
|
|
||||||
|
Ident :: distinct name.Name
|
||||||
|
Tag :: distinct name.Name
|
||||||
|
|
||||||
|
Atom :: union {
|
||||||
|
Ident,
|
||||||
|
Tag,
|
||||||
|
string,
|
||||||
|
f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
Sexp_List_Node :: struct {
|
||||||
|
expr: Sexp,
|
||||||
|
node: list.Node,
|
||||||
|
}
|
||||||
|
|
||||||
|
Sexp_List :: distinct list.List
|
||||||
|
|
||||||
|
Sexp :: union {
|
||||||
|
Atom,
|
||||||
|
Sexp_List,
|
||||||
|
}
|
||||||
|
|
||||||
|
to_ident :: proc(sexp: Sexp) -> (ident: Ident, ok: bool) {
|
||||||
|
atom: Atom
|
||||||
|
atom, ok = sexp.(Atom)
|
||||||
|
if ok {
|
||||||
|
ident, ok = atom.(Ident)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
to_ident_checked :: proc(sexp: Sexp, expr := #caller_expression) -> Ident {
|
||||||
|
ident, ok := to_ident(sexp)
|
||||||
|
assert(ok, expr)
|
||||||
|
return ident
|
||||||
|
}
|
||||||
|
|
||||||
|
is_ident_expr :: proc(sexp: Sexp) -> (ok: bool) {
|
||||||
|
atom: Atom
|
||||||
|
atom, ok = sexp.(Atom)
|
||||||
|
if ok {
|
||||||
|
_, ok = atom.(Ident)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
to_string_temp_sexp :: proc(sexp: Sexp) -> string {
|
||||||
|
builder := strings.builder_make(context.temp_allocator)
|
||||||
|
w := strings.to_writer(&builder)
|
||||||
|
print_sexp(sexp, w)
|
||||||
|
return strings.to_string(builder)
|
||||||
|
}
|
||||||
|
|
||||||
|
to_string_temp_iter :: proc(it: List_Iterator) -> string {
|
||||||
|
builder := strings.builder_make(context.temp_allocator)
|
||||||
|
w := strings.to_writer(&builder)
|
||||||
|
print_list(it, w)
|
||||||
|
return strings.to_string(builder)
|
||||||
|
}
|
||||||
|
|
||||||
|
to_string_temp :: proc {
|
||||||
|
to_string_temp_sexp,
|
||||||
|
to_string_temp_iter,
|
||||||
|
}
|
||||||
|
|
||||||
|
expect :: proc(ctx: ^SEXP_Parser, expected: rune) -> Error {
|
||||||
|
c, next := peek(ctx) or_return
|
||||||
|
|
||||||
|
if c != expected {
|
||||||
|
return make_error(ctx, fmt.tprintf("unexpected character {}, expected: {}", c, expected))
|
||||||
|
}
|
||||||
|
ctx.pos = next
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
is_ident :: proc(c: rune) -> bool {
|
||||||
|
switch c {
|
||||||
|
case 'a' ..= 'z', 'A' ..= 'Z', '0' ..= '9', '-', '_', '.':
|
||||||
|
return true
|
||||||
|
case:
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_atom :: proc(ctx: ^SEXP_Parser) -> (atom: Atom, error: Error) {
|
||||||
|
c, next := peek(ctx) or_return
|
||||||
|
|
||||||
|
switch c {
|
||||||
|
case 'a' ..= 'z', 'A' ..= 'Z':
|
||||||
|
start := ctx.pos
|
||||||
|
for ctx.pos < len(ctx.data) {
|
||||||
|
c, next = peek(ctx) or_return
|
||||||
|
if !is_ident(c) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.pos = next
|
||||||
|
}
|
||||||
|
return Ident(name.from_string(ctx.data[start:ctx.pos])), nil
|
||||||
|
case ':':
|
||||||
|
// skip it
|
||||||
|
ctx.pos = next
|
||||||
|
|
||||||
|
start := ctx.pos
|
||||||
|
for ctx.pos < len(ctx.data) {
|
||||||
|
c, next = peek(ctx) or_return
|
||||||
|
if !is_ident(c) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.pos = next
|
||||||
|
}
|
||||||
|
return Tag(name.from_string(ctx.data[start:ctx.pos])), nil
|
||||||
|
case '"':
|
||||||
|
// skip it
|
||||||
|
ctx.pos = next
|
||||||
|
|
||||||
|
start := ctx.pos
|
||||||
|
loop: for {
|
||||||
|
c, next = peek(ctx) or_return
|
||||||
|
|
||||||
|
switch c {
|
||||||
|
case '\\':
|
||||||
|
_, next = peek(ctx) or_return
|
||||||
|
ctx.pos = next
|
||||||
|
case '"':
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
ctx.pos = next
|
||||||
|
}
|
||||||
|
|
||||||
|
result := ctx.data[start:ctx.pos]
|
||||||
|
ctx.pos = next
|
||||||
|
return result, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, make_error(ctx, fmt.tprintf("unknown atom {}", c))
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_list :: proc(ctx: ^SEXP_Parser) -> (result: Sexp_List, error: Error) {
|
||||||
|
skip_whitespace(ctx) or_return
|
||||||
|
expect(ctx, '(') or_return
|
||||||
|
|
||||||
|
for {
|
||||||
|
skip_whitespace(ctx) or_return
|
||||||
|
c, next := peek(ctx) or_return
|
||||||
|
|
||||||
|
if c == ')' {
|
||||||
|
ctx.pos = next
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
sexp := parse_sexp(ctx) or_return
|
||||||
|
node := new(Sexp_List_Node)
|
||||||
|
node.expr = sexp
|
||||||
|
list.push_back(cast(^list.List)&result, &node.node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_sexp :: proc(ctx: ^SEXP_Parser) -> (sexp: Sexp, error: Error) {
|
||||||
|
skip_whitespace(ctx) or_return
|
||||||
|
c, _ := peek(ctx) or_return
|
||||||
|
|
||||||
|
switch c {
|
||||||
|
case '(':
|
||||||
|
return parse_list(ctx)
|
||||||
|
case:
|
||||||
|
return parse_atom(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parses a top level list of s-expressions which are not enclosed by parens
|
||||||
|
// No freeing procedure is provided, instead pass in an arena allocator and call free_all on it
|
||||||
|
parse :: proc(
|
||||||
|
ctx: ^SEXP_Parser,
|
||||||
|
allocator := context.temp_allocator,
|
||||||
|
) -> (
|
||||||
|
sexp: Sexp_List,
|
||||||
|
error: Error,
|
||||||
|
) {
|
||||||
|
context.allocator = allocator
|
||||||
|
|
||||||
|
sexp_list: Sexp_List
|
||||||
|
|
||||||
|
for ctx.pos < len(ctx.data) {
|
||||||
|
expr := parse_sexp(ctx) or_return
|
||||||
|
node := new(Sexp_List_Node)
|
||||||
|
node.expr = expr
|
||||||
|
list.push_back(cast(^list.List)&sexp_list, &node.node)
|
||||||
|
|
||||||
|
skip_whitespace(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sexp_list, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
List_Iterator :: list.Iterator(Sexp_List_Node)
|
||||||
|
|
||||||
|
iterator_list :: proc(sexp_list: Sexp_List) -> List_Iterator {
|
||||||
|
return list.iterator_head(list.List(sexp_list), Sexp_List_Node, "node")
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator_next :: proc(it: ^List_Iterator) -> (^Sexp_List_Node, bool) {
|
||||||
|
return list.iterate_next(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator_next_checked :: proc(it: ^List_Iterator) -> Sexp {
|
||||||
|
node, ok := iterator_next(it)
|
||||||
|
assert(ok)
|
||||||
|
|
||||||
|
return node.expr
|
||||||
|
}
|
||||||
|
|
||||||
|
print_list :: proc(it: List_Iterator, w: io.Writer) -> io.Error {
|
||||||
|
it := it
|
||||||
|
io.write_byte(w, '(') or_return
|
||||||
|
first := true
|
||||||
|
for sexp_node in list.iterate_next(&it) {
|
||||||
|
if !first {
|
||||||
|
io.write_byte(w, ' ') or_return
|
||||||
|
}
|
||||||
|
print_sexp(sexp_node.expr, w) or_return
|
||||||
|
first = false
|
||||||
|
}
|
||||||
|
io.write_byte(w, ')') or_return
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
print_sexp :: proc(sexp: Sexp, w: io.Writer) -> io.Error {
|
||||||
|
switch s in sexp {
|
||||||
|
case Atom:
|
||||||
|
switch a in s {
|
||||||
|
case Ident:
|
||||||
|
io.write_string(w, string(name.to_string(name.Name(a)))) or_return
|
||||||
|
case Tag:
|
||||||
|
io.write_byte(w, ':') or_return
|
||||||
|
io.write_string(w, string(name.to_string(name.Name(a)))) or_return
|
||||||
|
case string:
|
||||||
|
io.write_quoted_string(w, a, '"') or_return
|
||||||
|
case f64:
|
||||||
|
io.write_f64(w, a) or_return
|
||||||
|
}
|
||||||
|
case Sexp_List:
|
||||||
|
it := list.iterator_head(list.List(s), Sexp_List_Node, "node")
|
||||||
|
print_list(it, w) or_return
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
Error_Type :: struct {
|
||||||
|
msg: string,
|
||||||
|
pos: int,
|
||||||
|
}
|
||||||
|
|
||||||
|
Error :: union {
|
||||||
|
Error_Type,
|
||||||
|
}
|
||||||
|
|
||||||
|
make_error :: proc(ctx: ^SEXP_Parser, err: string) -> Error {
|
||||||
|
return Error_Type{msg = err, pos = ctx.pos}
|
||||||
|
}
|
||||||
|
|
||||||
|
get_line_column :: proc(str: string, pos: int) -> (line: int, column: int) {
|
||||||
|
line, column = 0, 0
|
||||||
|
|
||||||
|
p := 0
|
||||||
|
|
||||||
|
for p < pos {
|
||||||
|
c, num_bytes := utf8.decode_rune(str[p:])
|
||||||
|
p += num_bytes
|
||||||
|
if c == '\n' {
|
||||||
|
line += 1
|
||||||
|
column = 0
|
||||||
|
} else {
|
||||||
|
column += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return line, column
|
||||||
|
}
|
||||||
|
|
||||||
|
get_line_string :: proc(str: string, line: int) -> string {
|
||||||
|
cur_line := 0
|
||||||
|
p := 0
|
||||||
|
|
||||||
|
start := -1
|
||||||
|
for p < len(str) {
|
||||||
|
c, num_bytes := utf8.decode_rune(str[p:])
|
||||||
|
if c == '\n' {
|
||||||
|
cur_line += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if cur_line == line + 1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
p += num_bytes
|
||||||
|
if cur_line == line && start == -1 {
|
||||||
|
start = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if start != -1 {
|
||||||
|
return str[start:p]
|
||||||
|
} else {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
peek :: proc(ctx: ^SEXP_Parser) -> (c: rune, next: int, err: Error) {
|
||||||
|
if ctx.pos < len(ctx.data) {
|
||||||
|
num_bytes: int
|
||||||
|
c, num_bytes = utf8.decode_rune(ctx.data[ctx.pos:])
|
||||||
|
if c == utf8.RUNE_ERROR {
|
||||||
|
return c, ctx.pos, make_error(ctx, "invalid utf8 rune")
|
||||||
|
}
|
||||||
|
return c, ctx.pos + num_bytes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, 0, make_error(ctx, "unexpected EOF")
|
||||||
|
}
|
||||||
|
|
||||||
|
skip_whitespace :: proc(ctx: ^SEXP_Parser) -> Error {
|
||||||
|
if ctx.pos == len(ctx.data) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
c, next := peek(ctx) or_return
|
||||||
|
|
||||||
|
if c == ';' {
|
||||||
|
skip_until_newline(ctx) or_return
|
||||||
|
} else if unicode.is_white_space(c) {
|
||||||
|
ctx.pos = next
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
skip_until_newline :: proc(ctx: ^SEXP_Parser) -> Error {
|
||||||
|
for {
|
||||||
|
c, next := peek(ctx) or_return
|
||||||
|
ctx.pos = next
|
||||||
|
if c == '\n' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
print_pretty_error :: proc(w: io.Writer, data: string, error: Error) -> io.Error {
|
||||||
|
if error == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err := error.(Error_Type)
|
||||||
|
line, column := get_line_column(data, err.pos)
|
||||||
|
line_str := get_line_string(data, line)
|
||||||
|
|
||||||
|
io.write_byte(w, '\n')
|
||||||
|
|
||||||
|
offset: int
|
||||||
|
io.write_int(w, line + 1, 10, &offset) or_return
|
||||||
|
io.write_string(w, " | ") or_return
|
||||||
|
io.write_string(w, line_str) or_return
|
||||||
|
io.write_byte(w, '\n') or_return
|
||||||
|
|
||||||
|
for _ in 0 ..< (offset + 3 + column) {
|
||||||
|
io.write_byte(w, ' ')
|
||||||
|
}
|
||||||
|
io.write_string(w, "^ ")
|
||||||
|
io.write_string(w, err.msg)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
test_parse :: proc(t: ^testing.T) {
|
||||||
|
ctx: SEXP_Parser
|
||||||
|
ctx.data = "ident (sexp with \"string\") (nested (sexp))"
|
||||||
|
|
||||||
|
sexp, err := parse(&ctx)
|
||||||
|
|
||||||
|
testing.expect_value(t, err, nil)
|
||||||
|
|
||||||
|
builder: strings.Builder
|
||||||
|
strings.builder_init(&builder, context.temp_allocator)
|
||||||
|
|
||||||
|
writer := strings.to_writer(&builder)
|
||||||
|
print_sexp(sexp, writer)
|
||||||
|
|
||||||
|
printed := strings.to_string(builder)
|
||||||
|
|
||||||
|
testing.expect_value(t, printed, "(ident (sexp with \"string\") (nested (sexp)))")
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
test_error :: proc(t: ^testing.T) {
|
||||||
|
ctx: SEXP_Parser
|
||||||
|
ctx.data = `
|
||||||
|
ident
|
||||||
|
(sexp with "string"`
|
||||||
|
|
||||||
|
|
||||||
|
_, err := parse(&ctx)
|
||||||
|
|
||||||
|
testing.expect(t, err != nil)
|
||||||
|
|
||||||
|
builder: strings.Builder
|
||||||
|
strings.builder_init(&builder, context.temp_allocator)
|
||||||
|
|
||||||
|
writer := strings.to_writer(&builder)
|
||||||
|
print_pretty_error(writer, ctx.data, err)
|
||||||
|
printed := strings.to_string(builder)
|
||||||
|
|
||||||
|
testing.expect_value(
|
||||||
|
t,
|
||||||
|
printed,
|
||||||
|
`
|
||||||
|
3 | (sexp with "string"
|
||||||
|
^ unexpected EOF`,
|
||||||
|
)
|
||||||
|
}
|
@ -6,6 +6,10 @@ import "core:mem"
|
|||||||
import "core:strings"
|
import "core:strings"
|
||||||
import "core:sync"
|
import "core:sync"
|
||||||
|
|
||||||
|
// When enabled name globals will be initialized automatically
|
||||||
|
NAME_STATIC_INIT :: #config(NAME_STATIC_INIT, true)
|
||||||
|
MAX_STATIC_NAMES :: #config(MAX_STATIC_NAMES, 1024)
|
||||||
|
|
||||||
Name :: distinct u32
|
Name :: distinct u32
|
||||||
|
|
||||||
NONE :: Name(0)
|
NONE :: Name(0)
|
||||||
@ -27,6 +31,7 @@ setup_global_container :: proc(cnt: ^Container) {
|
|||||||
init :: proc(cnt: ^Container) {
|
init :: proc(cnt: ^Container) {
|
||||||
mem.dynamic_arena_init(&cnt.names_allocator)
|
mem.dynamic_arena_init(&cnt.names_allocator)
|
||||||
assert(len(cnt.names_array) == 0)
|
assert(len(cnt.names_array) == 0)
|
||||||
|
|
||||||
append(&cnt.names_array, "None")
|
append(&cnt.names_array, "None")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,6 +44,22 @@ destroy :: proc() {
|
|||||||
global_container = nil
|
global_container = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
when NAME_STATIC_INIT {
|
||||||
|
@(private = "file")
|
||||||
|
static_container: Container
|
||||||
|
|
||||||
|
@(init)
|
||||||
|
init_static :: proc() {
|
||||||
|
init(&static_container)
|
||||||
|
setup_global_container(&static_container)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(fini)
|
||||||
|
fini_static :: proc() {
|
||||||
|
destroy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
from_string :: proc(str: string) -> Name {
|
from_string :: proc(str: string) -> Name {
|
||||||
sync.atomic_rw_mutex_guard(&global_container.lock)
|
sync.atomic_rw_mutex_guard(&global_container.lock)
|
||||||
existing, ok := global_container.names_lookup[str]
|
existing, ok := global_container.names_lookup[str]
|
@ -37,6 +37,6 @@ test_curve_parsing_error :: proc(t: ^testing.T) {
|
|||||||
curve, err := parse_csv_2d(transmute([]u8)test_invalid_csv, context.temp_allocator)
|
curve, err := parse_csv_2d(transmute([]u8)test_invalid_csv, context.temp_allocator)
|
||||||
defer free_all(context.temp_allocator)
|
defer free_all(context.temp_allocator)
|
||||||
|
|
||||||
testing.expect_value(t, err, CSV_Parse_Error.CSV_Parse_Error)
|
testing.expect_value(t, err, CSV_Parse_Error.ExpectedNumber)
|
||||||
testing.expect_value(t, len(curve.points), 0)
|
testing.expect_value(t, len(curve.points), 0)
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
package game
|
package game
|
||||||
|
|
||||||
import "assets"
|
import "assets"
|
||||||
|
import "common:name"
|
||||||
import "core:fmt"
|
import "core:fmt"
|
||||||
import "core:hash"
|
import "core:hash"
|
||||||
import "core:log"
|
import "core:log"
|
||||||
@ -26,7 +27,6 @@ import "game:render"
|
|||||||
import rl "libs:raylib"
|
import rl "libs:raylib"
|
||||||
import "libs:raylib/rlgl"
|
import "libs:raylib/rlgl"
|
||||||
import "libs:tracy"
|
import "libs:tracy"
|
||||||
import "name"
|
|
||||||
import "ui"
|
import "ui"
|
||||||
|
|
||||||
PIXEL_WINDOW_HEIGHT :: 360
|
PIXEL_WINDOW_HEIGHT :: 360
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package physics
|
package physics
|
||||||
|
|
||||||
import "bvh"
|
import "bvh"
|
||||||
|
import "common:name"
|
||||||
import "core:fmt"
|
import "core:fmt"
|
||||||
import "core:log"
|
import "core:log"
|
||||||
import "core:math"
|
import "core:math"
|
||||||
@ -10,7 +11,6 @@ import "core:slice"
|
|||||||
import "core:strings"
|
import "core:strings"
|
||||||
import "game:debug"
|
import "game:debug"
|
||||||
import he "game:halfedge"
|
import he "game:halfedge"
|
||||||
import "game:name"
|
|
||||||
import "game:ui"
|
import "game:ui"
|
||||||
import rl "libs:raylib"
|
import rl "libs:raylib"
|
||||||
import "libs:raylib/rlgl"
|
import "libs:raylib/rlgl"
|
||||||
|
@ -2,9 +2,9 @@ package physics
|
|||||||
|
|
||||||
import "bvh"
|
import "bvh"
|
||||||
import "collision"
|
import "collision"
|
||||||
|
import "common:name"
|
||||||
import lg "core:math/linalg"
|
import lg "core:math/linalg"
|
||||||
import "game:container/spanpool"
|
import "game:container/spanpool"
|
||||||
import "game:name"
|
|
||||||
import "libs:tracy"
|
import "libs:tracy"
|
||||||
|
|
||||||
MAX_CONTACTS :: 1024 * 16
|
MAX_CONTACTS :: 1024 * 16
|
||||||
|
@ -3,6 +3,7 @@ package physics
|
|||||||
import "base:runtime"
|
import "base:runtime"
|
||||||
import "bvh"
|
import "bvh"
|
||||||
import "collision"
|
import "collision"
|
||||||
|
import "common:name"
|
||||||
import "core:container/bit_array"
|
import "core:container/bit_array"
|
||||||
import "core:fmt"
|
import "core:fmt"
|
||||||
import "core:log"
|
import "core:log"
|
||||||
@ -12,7 +13,6 @@ import "core:math/rand"
|
|||||||
import "core:slice"
|
import "core:slice"
|
||||||
import "game:debug"
|
import "game:debug"
|
||||||
import he "game:halfedge"
|
import he "game:halfedge"
|
||||||
import "game:name"
|
|
||||||
import "libs:tracy"
|
import "libs:tracy"
|
||||||
|
|
||||||
_ :: name
|
_ :: name
|
||||||
|
23
src_assets/defs.ass
Normal file
23
src_assets/defs.ass
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
(def nil ())
|
||||||
|
|
||||||
|
(defn blender-script (script-file blend-file)
|
||||||
|
(run-proc "blender" "--python" script-file blend-file :out-file))
|
||||||
|
|
||||||
|
(defn export-model-from-collection-blend (blend-file collection)
|
||||||
|
(blender-script "export-model-from-collection.py" blend-file))
|
||||||
|
|
||||||
|
(path-join "path" "components" "joined" "together")
|
||||||
|
|
||||||
|
(define-asset :model "models.ae86"
|
||||||
|
:format :glb
|
||||||
|
:make (let (tmp (temp-dir "mytemp"))
|
||||||
|
(run-proc tmp "test.blend")
|
||||||
|
(export-model-from-collection-blend "ae86.blend" "Final")))
|
||||||
|
|
||||||
|
(def textures-in-file (blender-script "list_textures.py" "testblend.blend"))
|
||||||
|
|
||||||
|
; (for textures-in-file )
|
||||||
|
|
||||||
|
; (define-asset :car "cars/ae86.car"
|
||||||
|
; (visual-model (export-model-from-collection (load-blend "ae86.blend") "Final"))
|
||||||
|
; (collision (export-collision-from-collection (load-blend "car_convex.blend") "Collision")))
|
2
test.sh
2
test.sh
@ -1,3 +1,3 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
odin test game/assets -collection:common=./common -collection:game=./game -collection:libs=./libs -strict-style -vet -sanitize:memory
|
odin test common/encoding/sexp -collection:common=./common -collection:game=./game -collection:libs=./libs -strict-style -vet -sanitize:memory
|
||||||
|
Loading…
x
Reference in New Issue
Block a user