package core.intrinsics.onyx
+//
+// Special intrinsic function that initializes a value.
+// This is generally used on structure to execute their
+// default initializer expressions.
__initialize :: (val: ^$T) -> void #intrinsic ---
+//
+// Helpful macro to create an initialized value.
+// Mostly pointless now that T.{} does the same thing.
init :: macro ($T: type_expr) -> T {
__initialize :: __initialize
package core.arg_parse
+//
+// This is currently a very basic argument parsing library.
+// The options are given through a structure like so:
+//
+// Options :: struct {
+// @"--option_1"
+// option_1: str;
+//
+// @"--option_2", "-o2"
+// option_2: bool;
+// }
+//
+// main :: (args) => {
+// o: Options;
+// arg_parse.arg_parse(args, ^o);
+// }
+//
+// Options that are strings and integers expect an argument after
+// them to specify their value. Options that are bool default to
+// false and are true if one or more of the option values are present.
+//
+
use core
arg_parse :: (c_args: [] cstr, output: any) -> bool {
package core
+//
+// This file defines an optional language feature called method operators.
+// Instead of defining an operator overload using the #operator directive,
+// one can simply define a __op method in the structure to define how to
+// perform the operations. For example,
+//
+// Vec2 :: struct { x, y: f32 }
+//
+// #inject Vec2 {
+// __add :: (v1, v2: Vec2) => Vec2.{ v1.x + v2.x, v1.y + v2.y };
+// }
+//
+// This is an optional language feature because it currently significantly
+// affects compile-time, on average adding 30% to the total compilation time.
+// To enable this feature, add this somewhere:
+//
+// #inject runtime.vars.Onyx_Enable_Operator_Methods :: true
+//
+
#local {
__HasEqMethod :: interface (t: $T, r: $R) { T.__eq(t, r); }
__HasNeMethod :: interface (t: $T, r: $R) { T.__ne(t, r); }
package core
+//
+// Sometimes when interfacing with libraries from C, it is really helpful
+// to be able to represent pointers to things outside of the bounds of the
+// WASM memory. For one concrete example, glGetString takes an enum and
+// procudes a character pointer to a static string with that enum's name.
+// Without this library, it would be impossible to access this memory,
+// without first copying it into the WASM memory. That requires a lot of
+// overhead, so instead this library allows Onxy to access memory outside
+// the normal bounds.
+//
+// cptr(T) is a C-pointer to a value of type T. It has a couple useful
+// methods, namely: read(), extract_str(), and to_rawptr().
+//
+
@conv.Custom_Format.{ #solidify cptr.format {T=T} }
cptr :: struct (T: type_expr) {
data: u64;
}
#inject cptr {
+ //
+ // Creates a new C-pointer from an Onyx pointer.
make :: macro (ptr: ^$T) -> cptr(T) {
__cptr_make :: __cptr_make
return .{ __cptr_make(ptr) };
}
+ //
+ // Extract the data out of a C pointer into a buffer in the Onyx memory.
read :: (this: cptr($T)) -> T {
buf: [sizeof T] u8;
__cptr_read(this.data, ~~buf, sizeof T);
return *cast(^T) buf;
}
+ //
+ // Helper procedures for quickly reading an integer of various sizes.
read_u8 :: (this: cptr(u8)) => __cptr_read_u8(this.data);
read_u16 :: (this: cptr(u16)) => __cptr_read_u16(this.data);
read_u32 :: (this: cptr(u32)) => __cptr_read_u32(this.data);
read_i32 :: (this: cptr(i32)) => cast(i32) __cptr_read_u32(this.data);
read_i64 :: (this: cptr(i64)) => cast(i64) __cptr_read_u64(this.data);
+ //
// When given a non-zero-sized dest, this procedure
// fills the dest buffer with the contents of the string
// up to the number bytes in the dest buffer. This
// using __cptr_read_u8 would be slow compared to strlen().
extract_str :: (this: cptr(u8), dest: [] u8) => __cptr_extract_str(this.data, dest);
+ //
+ // This procedure attempts to convert a C-pointer back into an
+ // Onyx pointer, if the pointer lives with the Onyx memory space.
to_rawptr :: (this: cptr($T)) -> ^T {
// I'm treating NULL as more of a concept, than as an actual value here,
// because if something returns a NULL pointer, it should logically map
}
}
+
+//
+// Allows for pointer addition.
#operator + macro (p: cptr($T), v: i32) -> cptr(T) {
return .{ p.data + ~~(v * sizeof T) };
}
thread_map : Map(Thread_ID, ^Thread);
}
+//
+// An id of a thread.
Thread_ID :: #type i32
+//
+// Represents a thread. Currently, this is very simple; just the id
+// of the thread and whether or not it is alive.
Thread :: struct {
id : Thread_ID;
alive : bool;
}
+//
+// Spawns a new thread using the runtime.__spawn_thread function.
+// The primary job of this function is to create the thread-local
+// storage and stack for the new thread, and pass those on.
+// Currently the stack size is not controllable, but that could
+// be remedied.
spawn :: (t: ^Thread, data: ^$T, func: (^T) -> void) {
sync.scoped_mutex(^thread_mutex);
runtime.__spawn_thread(t.id, tls_base, stack_base, func, data);
}
+//
+// Waits for a thread to finish before returning.
+// If the thread was not alive in the first place,
+// immediately return.
join :: (t: ^Thread) {
while t.alive {
#if runtime.Wait_Notify_Available {
}
}
+//
+// Forcefully kill a thread using runtime.__kill_thread.
+// Does nothing if the thread was not alive.
kill :: (t: ^Thread) -> i32 {
if !t.alive do return -1;
return 1;
}
+//
+// Special procedure that should only be called once globally
+// that initialize the map of thread ids to thread data.
__initialize :: () {
thread_map->init();
}
+//
+// Special procedure that is called when a thread exits,
+// or by kill() above.
__exited :: (id: i32) {
sync.scoped_mutex(^thread_mutex);