Last active
February 28, 2026 22:50
-
-
Save rezich/4127f1511aa9a59d9d61edfa7832760a to your computer and use it in GitHub Desktop.
Some ideas for how to structure a widget-based GUI editor system for a game, which demonstrate some advanced things you can do with the language—not all of which are necessarily advisable, but most if not all of which are hopefully educational
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // | |
| // Say you're making some kind of game that involves "Nodes" being placed around | |
| // the game world, and you want to make an editor mode (disabled in release | |
| // builds) that lets you create and manipulate these Nodes. | |
| // | |
| // Setting aside data serialization and an undo/redo system, here is a way that | |
| // you could make such an editor system, that gives you an immediate-mode-ish | |
| // API for easily creating editor-mode "widgets" each frame, with specific | |
| // interaction and rendering behaviors for each different kind of "widget". | |
| // | |
| // Doing this will involve using a few neat tricks which the language currently | |
| // permits, but which may not be immediately obvious to people coming from other | |
| // languages. | |
| // | |
| // This information is presented for purely informational reasons and does not | |
| // necessarily reflect best practices or whatever blah blah blah. But maybe, | |
| // it's not really so bad. | |
| // | |
| // To see what things look like when you *really* go off the rails into | |
| // crazytown and disregard how_to/999_temperance.jai in its entirety, stick | |
| // around for the follow-ups after this. | |
| // | |
| // Game code ------------------------------------------------------------------- | |
| Node :: struct { | |
| ID :: #type,isa u64; | |
| id: ID; | |
| position: Vector3; | |
| color: Vector4; | |
| } | |
| nodes: [..] Node; | |
| node_get :: (node_id: Node.ID) -> *Node { | |
| for * nodes if it.id == node_id then return it; | |
| return null; | |
| } | |
| // Entry point / main loop ----------------------------------------------------- | |
| EDITOR_ENABLED :: true; // Set to false in release builds | |
| main :: () { | |
| //TODO: Add some Nodes to nodes, probably | |
| while true { | |
| defer reset_temporary_storage(); | |
| // Handle input | |
| #if EDITOR_ENABLED { | |
| //TODO: Toggle is_editing when you press some keyboard key | |
| if is_editing { | |
| widgets_reset(); | |
| for nodes widget_node(it.id); | |
| //TODO: More widgets for more things in the game in the future | |
| widgets_handle_input(); | |
| } | |
| } | |
| // Simulate game state | |
| //TODO: A game (or at least something that does something with nodes) | |
| // Render | |
| //TODO: Render nodes as they would appear in the game | |
| #if EDITOR_ENABLED then if is_editing then widgets_draw(); | |
| } | |
| } | |
| #import "Basic"; | |
| #import "Math"; | |
| // Editor widgets -------------------------------------------------------------- | |
| // (Probably in a separate file, only #load'd #if EDITOR_ENABLED) | |
| #if EDITOR_ENABLED { | |
| // This is the data structure for the widget that represents Nodes in the | |
| // editor | |
| Widget_Node :: struct { | |
| SIZE :: 5; | |
| node_id: Node.ID; | |
| } | |
| // This is the "make a widget" procedure that you invoke to make a widget | |
| // that represents a Node in the editor after you invoke widgets_reset() | |
| widget_node :: (node_id: Node.ID, loc := #caller_location) { | |
| // As per the instructions in widget() (below), we define WIDGET_DATA | |
| // here as being Widget_Node, the data type associated with this kind of | |
| // widget | |
| WIDGET_DATA :: Widget_Node; | |
| node := node_get(node_id); | |
| if !node then return; | |
| widget(node.position, { | |
| on_click = (using w: *Widget) { use_data(); | |
| // Because we wrote `using w` above, we have unqualified access | |
| // to the contents of w in this scope, | |
| // * * * AND, ALSO, * * * | |
| // because we invoked use_data() above, we ALSO have unqualified | |
| // access to the contents of w.data -- AUTOMATICALLY CAST as a | |
| // *WIDGET_DATA -- when WIDGET_DATA is defined in the | |
| // widget_node() procedure-level scope, and not here in the | |
| // procedure literal that we're assigning the value of to the | |
| // on_click procedure pointer in the Widget.Procs struct | |
| // literal! | |
| // | |
| // Turns out, You Can Just Do That -- neat! | |
| log("Hello, sailor! I'm %, my node_id is %", hash, node_id); | |
| select_this(); | |
| }, | |
| draw = (using w: Widget) { use_data(); | |
| node := node_get(node_id); // from w.data.(*WIDGET_DATA) | |
| if !node then return; | |
| draw_something( | |
| position = position, // from w | |
| size = SIZE, // from w.data.(*WIDGET_DATA) | |
| color = node.color | |
| ); | |
| } | |
| }, Widget_Node.{ | |
| node_id = node_id | |
| }, loc, xx node_id); | |
| } | |
| } | |
| // Graphics engine ------------------------------------------------------------- | |
| // (In your game engine module, which is #import'd in the main file) | |
| // Draw something idk | |
| draw_something :: ( | |
| position: Vector3, | |
| size: float, | |
| color: Vector4 | |
| ) { /* TODO: Draw something...? */ } | |
| // Editor system --------------------------------------------------------------- | |
| // (In your game engine module, which is #import'd in the main file) | |
| is_editing := false; // If true, we're in Editor Mode | |
| // Call these in your main loop | |
| widgets_reset :: () { array_reset_keeping_memory(*widgets); } | |
| widgets_draw :: () { for w: widgets if w.draw then w.draw(w); } | |
| widgets_handle_input :: () { | |
| w: *Widget; | |
| //TODO: Determine which widget in widgets is clicked, and point w to it | |
| if w && w.on_click then w.on_click(w); | |
| } | |
| // Use this in your "make a widget" procedures to make a widget, which should | |
| // always begin with: | |
| // | |
| // WIDGET_DATA :: Your_Widget_Data_Struct_Type; | |
| // | |
| widget :: ( | |
| position: Vector3, | |
| procs: Widget.Procs, | |
| data: $T, | |
| loc: Source_Code_Location, | |
| identifiers: ..int | |
| ) { | |
| make_hash_from_loc_and_identifiers :: ( | |
| loc: Source_Code_Location, | |
| identifiers: [] int | |
| ) -> Widget.Hash { | |
| return xx 0; //TODO | |
| } | |
| w := Widget.{ | |
| hash = make_hash_from_loc_and_identifiers(loc, identifiers), | |
| data = talloc(size_of(T)), | |
| type = T, | |
| procs = procs, | |
| position = position, | |
| }; | |
| w.data.(*T).* = data; | |
| array_add(*widgets, w); | |
| } | |
| #scope_file // We are now done with the "public API" of the editor system | |
| Widget :: struct { | |
| Hash :: #type,isa u64; | |
| hash: Hash; // Unique hash from source code location and other identifiers | |
| data: *void; // Void pointer to data, obviously | |
| type: Type; // The type of data (not used here but useful to have) | |
| // These are the procedure pointers that your widgets can define | |
| Procs :: struct { | |
| on_click: (widget: *Widget); | |
| draw : (widget: Widget); | |
| } | |
| using procs: Procs; | |
| position: Vector3; // Widget position in world space (or whatever) | |
| // | |
| // The following macros are namespaced within Widget, such that they can | |
| // only be accessed either by qualifying them with `Widget.`, *or* from some | |
| // place that has e.g. `using w: *Widget` or `using w: Widget` -- such as a | |
| // procedure that has that as one of its parameters, like the procs in | |
| // Widget.Procs, for instance! | |
| // | |
| // Because the latter is precisely the convention we want, "hiding" these | |
| // macros within the Widget namespace is maybe a kind of nice thing to do, | |
| // because in doing so, we've expressed that this is the intended usage! | |
| // | |
| // Also, this works, even though we're in #scope_file, because that's just | |
| // how it works: | |
| // | |
| // // main.jai | |
| // main :: () { | |
| // using x := make_x(); // ok! | |
| // y = 108; // ok! | |
| // z(); // ok! | |
| // } | |
| // #load "other.jai"; | |
| // | |
| // // other.jai | |
| // make_x :: () -> X { return {}; } | |
| // #scope_file | |
| // X :: struct { | |
| // y: int; | |
| // z :: () {} | |
| // } | |
| // | |
| // TL;DR use the following macros in your Widget.Procs procs | |
| // | |
| // Use this in your Widget.Procs procedures to automatically "using" the | |
| // contents of the widget's data -- automatically cast to the correct data | |
| // type, which you defined in your outer "make a widget" procedure as | |
| // WIDGET_DATA, as per the instructions above | |
| use_data :: (calling_scope := #caller_code) #expand { | |
| // I always put these dumb UTF-8 comment braces around Jai strings that | |
| // get #insert'd, both as a visual warning to make it stand out here in | |
| // the source code, and because it makes it easy to find code generation | |
| // issues by inspecting .build/.added_strings_w2.jai | |
| #insert,scope(calling_scope) #string JAI | |
| //╔════════════════════════════════════════════════════════════════════════════════════════════════╗ | |
| using _ := data.(*WIDGET_DATA); | |
| //╚════════════════════════════════════════════════════════════════════════════════════════════════╝ | |
| JAI; | |
| } | |
| // (Maybe instead of _, you could name it correctly_cast_data_pointer, or | |
| // something, if you want to be able to access the pointer directly for some | |
| // reason?) | |
| // Use this in your Widget.Procs procedures to select this widget | |
| select_this :: () #expand { selected_widget_hash = `hash; } | |
| // Use this in your Widget.Procs procedures to check whether or not this | |
| // widget is the currently-selected widget | |
| this_is_selected :: () -> bool #expand { return selected_widget_hash == `hash; } | |
| } | |
| widgets: [..] Widget; | |
| selected_widget_hash: Widget.Hash; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // | |
| // * * * BONUS ADVENTURES IN 999 VIOLATIONS * * * | |
| // | |
| // Maybe it's just too annoying to have to type | |
| // | |
| // on_whatever = (using w: *Widget) { use_data(); | |
| // }, | |
| // | |
| // for each proc in Widget.Procs, in your "make a widget" procedures, because | |
| // you have to type out the entire procedure signature each time, remember to | |
| // `using` the first parameter each time, *and* remember to invoke use_data() | |
| // each time -- **AND** you have to remember to declare WIDGET_DATA at | |
| // the top of the "make a widget" procedure -- all before you type any code | |
| // that does anything at all for your widget. | |
| // | |
| // What if, instead of having to make a "make a widget" procedure for each kind | |
| // of widget at all, we could just pass some Code literals to a polymorphic | |
| // widget(), and have it make the actual procedure headers and bodies for us, | |
| // then fill those into the procedure pointer members into the corresponding | |
| // Widget.Procs member of the procs member of the widget for us? | |
| // | |
| // As it turns out, this is entirely possible to achieve -- and you don't even | |
| // need a metaprogram to do it. | |
| // | |
| // Game code ------------------------------------------------------------------- | |
| // (Unchanged from the previous version) | |
| Node :: struct { | |
| ID :: #type,isa u64; | |
| id: ID; | |
| position: Vector3; | |
| color: Vector4; | |
| } | |
| nodes: [..] Node; | |
| node_get :: (node_id: Node.ID) -> *Node { | |
| for * nodes if it.id == node_id then return it; | |
| return null; | |
| } | |
| // Entry point / main loop ----------------------------------------------------- | |
| EDITOR_ENABLED :: true; // Set to false in release builds | |
| main :: () { | |
| //TODO: Add some Nodes to nodes, probably | |
| while true { | |
| defer reset_temporary_storage(); | |
| // Handle input | |
| #if EDITOR_ENABLED { | |
| //TODO: Toggle is_editing when you press some keyboard key | |
| if is_editing { | |
| widgets_reset(); | |
| // * * * | |
| // Now, instead of having a "make a widget" procedure for each | |
| // kind of widget, we just do it inline here. We no longer have | |
| // to write out the procedure signature for each one, nor | |
| // remember to write use_data() at the top of each one, nor do | |
| // we need to define WIDGET_DATA anywhere -- widget() now | |
| // handles all of that for us! | |
| // | |
| // The only concession we have to make is, instead of passing | |
| // a Widget.Procs struct with procedural literals assigned to | |
| // its members, we instead pass a polymorphic Widget_Proc_Defs | |
| // *type*, with Code literals assigned to its polymoprhic | |
| // parameters. | |
| // | |
| // If this seems confusing, don't worry, we'll explain things | |
| // more as we go. | |
| // * * * | |
| for nodes widget(it.position, Widget_Proc_Defs( | |
| on_click = #code { | |
| log("Hello, sailor! I'm %, my node_id is %", hash, node_id); | |
| select_this(); | |
| }, | |
| draw = #code { | |
| node := node_get(node_id); | |
| if !node then return; | |
| draw_something( | |
| position = position, | |
| size = SIZE, | |
| color = node.color | |
| ); | |
| } | |
| ), Widget_Node.{ node_id=it.id }, xx it.id); | |
| // * * * | |
| // Note that the actual code within each of these Code literals | |
| // is identical to its counterpart in the procedure literals | |
| // in widget_node() in the previous version -- the interface | |
| // with which you write your actual widget interaction/rendering | |
| // logic remains completely unchanged! | |
| // * * * | |
| //TODO: More widgets for more things in the game in the future | |
| widgets_handle_input(); | |
| } | |
| } | |
| // Simulate game state | |
| //TODO: A game (or at least something that does something with nodes) | |
| // Render | |
| //TODO: Render nodes as they would appear in the game | |
| #if EDITOR_ENABLED then if is_editing then widgets_draw(); | |
| } | |
| } | |
| #import "Basic"; | |
| #import "Math"; | |
| // Editor widgets -------------------------------------------------------------- | |
| // (Probably in a separate file, only #load'd #if EDITOR_ENABLED) | |
| #if EDITOR_ENABLED { | |
| // This is the data structure for the widget that represents Nodes in the | |
| // editor | |
| Widget_Node :: struct { | |
| SIZE :: 5; | |
| node_id: Node.ID; | |
| } | |
| // * * * | |
| // Hey look -- no more "make a widget" procedures, as promised! | |
| // * * * | |
| } | |
| // Graphics engine ------------------------------------------------------------- | |
| // (In your game engine module, which is #import'd in the main file) | |
| // (Unchanged from the previous version) | |
| // Draw something idk | |
| draw_something :: ( | |
| position: Vector3, | |
| size: float, | |
| color: Vector4 | |
| ) { /* TODO: Draw something...? */ } | |
| // Editor system --------------------------------------------------------------- | |
| // (In your game engine module, which is #import'd in the main file) | |
| is_editing := false; // If true, we're in Editor Mode | |
| // Call these in your main loop | |
| widgets_reset :: () { array_reset_keeping_memory(*widgets); } | |
| widgets_draw :: () { for w: widgets if w.draw then w.draw(w); } | |
| widgets_handle_input :: () { | |
| w: *Widget; | |
| //TODO: Determine which widget in widgets is clicked, and point w to it | |
| if w && w.on_click then w.on_click(w); | |
| } | |
| // * * * | |
| // The new, updated widget() procedure | |
| // * * * | |
| widget :: ( | |
| position: Vector3, | |
| $PROC_DEFS: Type, // Must be a Widget_Proc_Defs type! | |
| data: $WIDGET_DATA, | |
| identifiers: ..int, | |
| loc := #caller_location | |
| ) { | |
| // * * * | |
| // Check to make sure the second argument is actually a Widget_Proc_Defs and | |
| // not some random garbage the user passed in for some dumb reason | |
| // * * * | |
| #assert type_info(PROC_DEFS).name == "Widget_Proc_Defs"; | |
| // * * * | |
| // Build the procs variable that we will assign to the widget's procs member | |
| // * * * | |
| procs: Widget.Procs; | |
| #insert -> string { builder: String_Builder; | |
| for member, member_index: type_info(Widget.Procs).members { | |
| assert(member.type.type == .PROCEDURE); | |
| member_proc_type := member.type.(*Type_Info_Procedure); | |
| // * * * | |
| // One problem with this 999 complexity we have decided to undertake | |
| // here, is that now we are kind of screwed when it comes to | |
| // altering our editor widget system to support additional | |
| // Widget.Procs procedure pointers that have any other parameters. | |
| // | |
| // It *is* possible to work around this, but doing so is very messy, | |
| // very ugly, and then you find yourself ending up reading | |
| // how_to/999_temperance.jai again, and rethinking all of your life | |
| // choices that led you up to this point. | |
| // | |
| // But we've made it this far, so we might as well keep going, | |
| // right? | |
| // | |
| // For the purposes of this demonstration, and in order to maintain | |
| // some tiny shred of sanity, let's just assume that all | |
| // Widget.Procs members must be procedure pointers to procedures | |
| // that have either a Widget or a *Widget as its first and only | |
| // parameter. | |
| // * * * | |
| member_proc_first_argument_type_string: string; | |
| if member_proc_type.argument_types[0].type == { | |
| case .POINTER; member_proc_first_argument_type_string = "*Widget"; | |
| case .STRUCT; member_proc_first_argument_type_string = "Widget"; | |
| case; assert(false); | |
| } | |
| print(*builder, #string JAI | |
| //╔════════════════════════════════════════════════════════════════════════════════════════════════╗ | |
| #if PROC_DEFS.%1 != #code,null then procs.%1 = (using w: %2) { | |
| using _ := data.(*WIDGET_DATA); | |
| #insert,scope() PROC_DEFS.%1; | |
| }; | |
| //╚════════════════════════════════════════════════════════════════════════════════════════════════╝ | |
| JAI, member.name, member_proc_first_argument_type_string); | |
| // * * * | |
| // On the second #insert'd line of code there, above, we're directly | |
| // #insert-ing the code that was previously hackily #insert'd into | |
| // the calling scope with the use_data() macro in the previous | |
| // version -- now, we no longer need that to be a macro, since we're | |
| // already #insert-ing a string of code here anyway. | |
| // | |
| // But, it still works! WIDGET_DATA is still constant within the | |
| // scope of widget() -- now as a polymorphic parameter rather than | |
| // something you need to remember to define at the top of your | |
| // "make a widget" procedure -- but the result is still the same: | |
| // it gets "baked into" the procedure literal whose value we are | |
| // assigning to the procedure pointer member in procs! | |
| // * * * | |
| } | |
| return builder_to_string(*builder); | |
| } | |
| // * * * | |
| // For more explanation of what we just did and why we just did it, see the | |
| // long comment below, at the end of this file. | |
| // * * * | |
| make_hash_from_loc_and_identifiers :: ( | |
| loc: Source_Code_Location, | |
| identifiers: [] int | |
| ) -> Widget.Hash { | |
| return xx 0; //TODO | |
| } | |
| w := Widget.{ | |
| hash = make_hash_from_loc_and_identifiers(loc, identifiers), | |
| data = talloc(size_of(WIDGET_DATA)), | |
| type = WIDGET_DATA, | |
| procs = procs, | |
| position = position, | |
| }; | |
| w.data.(*WIDGET_DATA).* = data; | |
| array_add(*widgets, w); | |
| } | |
| #scope_file // We are now done with the "public API" of the editor system | |
| Widget :: struct { | |
| Hash :: #type,isa u64; | |
| hash: Hash; | |
| data: *void; | |
| type: Type; | |
| Procs :: struct { | |
| on_click: (widget: *Widget); | |
| draw : (widget: Widget); | |
| } | |
| using procs: Procs; | |
| position: Vector3; | |
| // * * * | |
| // These macros are still available for use within the Code literals you use | |
| // to define widget interaction/rendering behavior, in Widget_Proc_Defs, | |
| // just like before. | |
| // * * * | |
| select_this :: () #expand { selected_widget_hash = `hash; } | |
| this_is_selected :: () -> bool #expand { return selected_widget_hash == `hash; } | |
| } | |
| widgets: [..] Widget; | |
| selected_widget_hash: Widget.Hash; | |
| // * * * | |
| // Automatically generate a polymorphic Widget_Proc_Defs struct, with one | |
| // polymorphic parameter for each member of Widget.Procs, but of type Code | |
| // instead (each with a default value of #code,null -- so you can omit any that | |
| // you don't care to use for a given kind of widget, just as you could in the | |
| // previous version with a Widget.Procs literal). | |
| // * * * | |
| #insert -> string { builder: String_Builder; | |
| append(*builder, #string JAI | |
| //╔════════════════════════════════════════════════════════════════════════════════════════════════╗ | |
| Widget_Proc_Defs :: struct( | |
| //╚════════════════════════════════════════════════════════════════════════════════════════════════╝ | |
| JAI); | |
| members := type_info(Widget.Procs).members; | |
| for members print(*builder, #string JAI | |
| //╔════════════════════════════════════════════════════════════════════════════════════════════════╗ | |
| %1 := #code,null%2 | |
| //╚════════════════════════════════════════════════════════════════════════════════════════════════╝ | |
| JAI, it.name, ifx it_index != members.count-1 then ","); | |
| append(*builder, #string JAI | |
| //╔════════════════════════════════════════════════════════════════════════════════════════════════╗ | |
| ) {} | |
| //╚════════════════════════════════════════════════════════════════════════════════════════════════╝ | |
| JAI); | |
| // * * * | |
| // Yes, you read that last append() correctly -- Widget_Proc_Defs is a zero- | |
| // sized struct that doesn't have any members. We only care about the | |
| // polymorphic parameters, because they're constant, and seen as being | |
| // constant by the compiler when we want to inspect them in the #insert in | |
| // widget() that lets us do our dark magic. | |
| // * * * | |
| return builder_to_string(*builder); | |
| } | |
| // * * * | |
| // You may be wondering: wait, what? Why? Huh?! | |
| // | |
| // Well, we need the Codes to be constant, and if you have something like this: | |
| // | |
| // Some_Codes :: struct { a, b: Code; } | |
| // use_some_codes :: ($CODES: Some_Codes) { | |
| // #if CODES.a != #code,null then #insert CODES.a; | |
| // #if CODES.b != #code,null then #insert CODES.b; | |
| // } | |
| // main :: () { | |
| // use_some_codes({ | |
| // b = #code { log("B!"); } | |
| // }); | |
| // } | |
| // | |
| // This will fail to compile, because even though CODES is constant within the | |
| // body of use_some_codes(), its members are not! But if we do something like | |
| // this instead: | |
| // | |
| // Some_Codes :: struct( | |
| // A := #code,null, | |
| // B := #code,null | |
| // ) {}; | |
| // use_some_codes :: ($CODES: Type) { | |
| // #assert type_info(CODES).name == "Some_Codes"; | |
| // #if CODES.A != #code,null then #insert CODES.A; | |
| // #if CODES.B != #code,null then #insert CODES.B; | |
| // } | |
| // main :: () { | |
| // use_some_codes(Some_Codes( | |
| // B = #code { log("B!"); } | |
| // )); | |
| // } | |
| // | |
| // Then it works, because polymorphic parameters must be constant! | |
| // * * * | |
| // | |
| // Please reread and meditate upon how_to/999_temperance.jai now. | |
| // I will do so as well. | |
| // |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // | |
| // * * * FURTHER BONUS ADVENTURES IN 999 VIOLATIONS * * * | |
| // | |
| // What if instead of doing that nonsense with Widget_Proc_Defs that we were | |
| // doing last time -- instead, we just stuck those Code parameters directly into | |
| // widget(), at compile-time? | |
| // | |
| // Game code ------------------------------------------------------------------- | |
| // (Unchanged from the previous version) | |
| Node :: struct { | |
| ID :: #type,isa u64; | |
| id: ID; | |
| position: Vector3; | |
| color: Vector4; | |
| } | |
| nodes: [..] Node; | |
| node_get :: (node_id: Node.ID) -> *Node { | |
| for * nodes if it.id == node_id then return it; | |
| return null; | |
| } | |
| // Entry point / main loop ----------------------------------------------------- | |
| EDITOR_ENABLED :: true; // Set to false in release builds | |
| main :: () { | |
| //TODO: Add some Nodes to nodes, probably | |
| while true { | |
| defer reset_temporary_storage(); | |
| // Handle input | |
| #if EDITOR_ENABLED { | |
| //TODO: Toggle is_editing when you press some keyboard key | |
| if is_editing { | |
| widgets_reset(); | |
| // * * * | |
| // Now, instead of using Widget_Proc_Defs as we did before, | |
| // we're going to be generating polymorphic Code parameters | |
| // (with a default value of #code,null) for widget(), for each | |
| // Widget.Procs member. | |
| // | |
| // The only concession we have to make for doing so -- at least, | |
| // I'm *pretty* sure we have to do this -- is, we need to use | |
| // the identifiers parameter by name.(Though, if you need to use | |
| // multiple identifiers, you can totally list them as e.g. | |
| // | |
| // identifiers = xx it.id, xx something_else, xx whatever | |
| // | |
| // That's not too bad! | |
| // * * * | |
| for nodes widget(it.position, Widget_Node.{ node_id=it.id }, | |
| on_click = #code { | |
| log("Hello, sailor! I'm %, my node_id is %", hash, node_id); | |
| select_this(); | |
| }, | |
| draw = #code { | |
| node := node_get(node_id); | |
| if !node then return; | |
| draw_something( | |
| position = position, | |
| size = SIZE, | |
| color = node.color | |
| ); | |
| }, | |
| identifiers = xx it.id | |
| ); | |
| //TODO: More widgets for more things in the game in the future | |
| widgets_handle_input(); | |
| } | |
| } | |
| // Simulate game state | |
| //TODO: A game (or at least something that does something with nodes) | |
| // Render | |
| //TODO: Render nodes as they would appear in the game | |
| #if EDITOR_ENABLED then if is_editing then widgets_draw(); | |
| } | |
| } | |
| #import "Basic"; | |
| #import "Math"; | |
| // Editor widgets -------------------------------------------------------------- | |
| // (Probably in a separate file, only #load'd #if EDITOR_ENABLED) | |
| // (Unchanged from the previous version) | |
| #if EDITOR_ENABLED { | |
| // This is the data structure for the widget | |
| // that represents Nodes in the editor | |
| Widget_Node :: struct { | |
| SIZE :: 5; | |
| node_id: Node.ID; | |
| } | |
| } | |
| // Graphics engine ------------------------------------------------------------- | |
| // (In your game engine module, which is #import'd in the main file) | |
| // (Unchanged from the previous version) | |
| // Draw something idk | |
| draw_something :: ( | |
| position: Vector3, | |
| size: float, | |
| color: Vector4 | |
| ) { /* TODO: Draw something...? */ } | |
| // Editor system --------------------------------------------------------------- | |
| // (In your game engine module, which is #import'd in the main file) | |
| is_editing := false; // If true, we're in Editor Mode | |
| // Call these in your main loop | |
| widgets_reset :: () { array_reset_keeping_memory(*widgets); } | |
| widgets_draw :: () { for w: widgets if w.procs.draw then w.procs.draw(w); } | |
| widgets_handle_input :: () { | |
| w: *Widget; | |
| //TODO: Determine which widget in widgets is clicked, and point w to it | |
| if w && w.procs.on_click then w.procs.on_click(w); | |
| } | |
| // * * * | |
| // Note that we un-using'd Widget.procs, so we need to qualify its members | |
| // in the above code. | |
| // * * * | |
| // * * * | |
| // I'm not quite sure why, but we can't have widget() *not* in #scope_file, but | |
| // have Widget *in* #scope_file. But we can work around it by just renaming | |
| // widget() to __widget__(), and putting the following line of code here: | |
| // * * * | |
| widget :: __widget__; | |
| #scope_file | |
| // * * * | |
| // This time, we're going to just say screw it, let's generate and #insert the | |
| // entire widget() procedure at compile time, so we can add a polymorphic Code | |
| // parameter (with a default value of #code,null) for each Widget.Procs member. | |
| // | |
| // Also, we moved the data parameter to be second in the parameters list. | |
| // | |
| // It's going to get a bit messy. | |
| // * * * | |
| #insert -> string { builder: String_Builder; | |
| widget_procs_members := type_info(Widget.Procs).members; | |
| append(*builder, #string JAI | |
| //╔════════════════════════════════════════════════════════════════════════════════════════════════╗ | |
| __widget__ :: ( | |
| position: Vector3, | |
| data: $WIDGET_DATA, | |
| //╚════════════════════════════════════════════════════════════════════════════════════════════════╝ | |
| JAI); | |
| for member: widget_procs_members print(*builder, #string JAI | |
| //╔════════════════════════════════════════════════════════════════════════════════════════════════╗ | |
| $%1 := #code,null, | |
| //╚════════════════════════════════════════════════════════════════════════════════════════════════╝ | |
| JAI, member.name); | |
| append(*builder, #string JAI | |
| //╔════════════════════════════════════════════════════════════════════════════════════════════════╗ | |
| identifiers: ..int, | |
| loc := #caller_location | |
| ) { | |
| procs: Widget.Procs; | |
| //╚════════════════════════════════════════════════════════════════════════════════════════════════╝ | |
| JAI); | |
| for member, member_index: widget_procs_members { | |
| assert(member.type.type == .PROCEDURE); | |
| member_proc_type := member.type.(*Type_Info_Procedure); | |
| member_proc_first_argument_type_string: string; | |
| if member_proc_type.argument_types[0].type == { | |
| case .POINTER; member_proc_first_argument_type_string = "*Widget"; | |
| case .STRUCT; member_proc_first_argument_type_string = "Widget"; | |
| case; assert(false); | |
| } | |
| print(*builder, #string JAI | |
| //╔════════════════════════════════════════════════════════════════════════════════════════════════╗ | |
| #if %1 != #code,null then procs.%1 = (using w: %2) { | |
| using _ := data.(*WIDGET_DATA); | |
| #insert,scope() %1; | |
| }; | |
| //╚════════════════════════════════════════════════════════════════════════════════════════════════╝ | |
| JAI, member.name, member_proc_first_argument_type_string); | |
| // * * * | |
| // We had to change the above bit to qualify the procs member with | |
| // `procs.`, because we un-using'd it in Widget. | |
| // * * * | |
| } | |
| append(*builder, #string JAI | |
| //╔════════════════════════════════════════════════════════════════════════════════════════════════╗ | |
| make_hash_from_loc_and_identifiers :: ( | |
| loc: Source_Code_Location, | |
| identifiers: [] int | |
| ) -> Widget.Hash { | |
| return xx 0; //TODO | |
| } | |
| w := Widget.{ | |
| hash = make_hash_from_loc_and_identifiers(loc, identifiers), | |
| data = talloc(size_of(WIDGET_DATA)), | |
| type = WIDGET_DATA, | |
| procs = procs, | |
| position = position, | |
| }; | |
| w.data.(*WIDGET_DATA).* = data; | |
| array_add(*widgets, w); | |
| } | |
| //╚════════════════════════════════════════════════════════════════════════════════════════════════╝ | |
| JAI); | |
| return builder_to_string(*builder); | |
| } | |
| // * * * | |
| // Now do you see why I use those dumb UTF-8 comment-braces on #insert'd | |
| // strings? Not only does it make things easier to visually parse and help with | |
| // code generation debugging, but the secret third reason that I didn't | |
| // explicitly mention before is, you can easily see at a glance just how fucked | |
| // up your code generation is getting, so you can once again go back and reread | |
| // how_to/999_temperance.jai yet again. | |
| // * * * | |
| Widget :: struct { | |
| Hash :: #type,isa u64; | |
| hash: Hash; | |
| data: *void; | |
| type: Type; | |
| Procs :: struct { | |
| on_click: (widget: *Widget); | |
| draw : (widget: Widget); | |
| } | |
| // * * * | |
| // We've un-using'd procs here, because widget() now has a Code parameter | |
| // with the same name as each member of Procs, and each procedure literal | |
| // within widget() needs to be able to access those, but each also has | |
| // `using w`, so, if we didn't un-using procs here, it would shadow the | |
| // constant variable with the non-constant variable. | |
| // | |
| // But that's fine -- it just means that in widgets_draw(), | |
| // widget_handle_input(), and one place in widget(), we need to qualify the | |
| // procs members with `procs.`. | |
| // * * * | |
| procs: Procs; | |
| position: Vector3; | |
| select_this :: () #expand { selected_widget_hash = `hash; } | |
| this_is_selected :: () -> bool #expand { return selected_widget_hash == `hash; } | |
| } | |
| widgets: [..] Widget; | |
| selected_widget_hash: Widget.Hash; | |
| // | |
| // Once again: | |
| // | |
| // Please reread and meditate upon how_to/999_temperance.jai now. | |
| // I will do so as well. | |
| // |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment