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,
|
||||
Desktop,
|
||||
Web,
|
||||
Assetcomp,
|
||||
}
|
||||
|
||||
Options :: struct {
|
||||
@ -282,6 +283,7 @@ main :: proc() {
|
||||
"odin",
|
||||
"build",
|
||||
"game",
|
||||
"-define:NAME_STATIC_INIT=false",
|
||||
"-define:RAYLIB_SHARED=true",
|
||||
"-define:PHYSFS_SHARED=true",
|
||||
"-define:DEV=true",
|
||||
@ -371,5 +373,20 @@ main :: proc() {
|
||||
),
|
||||
"./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: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
|
||||
|
||||
NONE :: Name(0)
|
||||
@ -27,6 +31,7 @@ setup_global_container :: proc(cnt: ^Container) {
|
||||
init :: proc(cnt: ^Container) {
|
||||
mem.dynamic_arena_init(&cnt.names_allocator)
|
||||
assert(len(cnt.names_array) == 0)
|
||||
|
||||
append(&cnt.names_array, "None")
|
||||
}
|
||||
|
||||
@ -39,6 +44,22 @@ destroy :: proc() {
|
||||
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 {
|
||||
sync.atomic_rw_mutex_guard(&global_container.lock)
|
||||
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)
|
||||
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)
|
||||
}
|
||||
|
@ -15,6 +15,7 @@
|
||||
package game
|
||||
|
||||
import "assets"
|
||||
import "common:name"
|
||||
import "core:fmt"
|
||||
import "core:hash"
|
||||
import "core:log"
|
||||
@ -26,7 +27,6 @@ import "game:render"
|
||||
import rl "libs:raylib"
|
||||
import "libs:raylib/rlgl"
|
||||
import "libs:tracy"
|
||||
import "name"
|
||||
import "ui"
|
||||
|
||||
PIXEL_WINDOW_HEIGHT :: 360
|
||||
|
@ -1,6 +1,7 @@
|
||||
package physics
|
||||
|
||||
import "bvh"
|
||||
import "common:name"
|
||||
import "core:fmt"
|
||||
import "core:log"
|
||||
import "core:math"
|
||||
@ -10,7 +11,6 @@ import "core:slice"
|
||||
import "core:strings"
|
||||
import "game:debug"
|
||||
import he "game:halfedge"
|
||||
import "game:name"
|
||||
import "game:ui"
|
||||
import rl "libs:raylib"
|
||||
import "libs:raylib/rlgl"
|
||||
|
@ -2,9 +2,9 @@ package physics
|
||||
|
||||
import "bvh"
|
||||
import "collision"
|
||||
import "common:name"
|
||||
import lg "core:math/linalg"
|
||||
import "game:container/spanpool"
|
||||
import "game:name"
|
||||
import "libs:tracy"
|
||||
|
||||
MAX_CONTACTS :: 1024 * 16
|
||||
|
@ -3,6 +3,7 @@ package physics
|
||||
import "base:runtime"
|
||||
import "bvh"
|
||||
import "collision"
|
||||
import "common:name"
|
||||
import "core:container/bit_array"
|
||||
import "core:fmt"
|
||||
import "core:log"
|
||||
@ -12,7 +13,6 @@ import "core:math/rand"
|
||||
import "core:slice"
|
||||
import "game:debug"
|
||||
import he "game:halfedge"
|
||||
import "game:name"
|
||||
import "libs:tracy"
|
||||
|
||||
_ :: 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
|
||||
|
||||
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