19 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
Gurted is a browser/wayfinder for the GURT protocol built in Godot 4.4. It renders HTML-like content with CSS-like styling using Godot's UI system and supports a custom utility-class-based styling system similar to Tailwind CSS.
Notes
- Do not prefix functions or variable names with _ (underscore), those are reserved for event functions.
- Do not try to "try" your additions via running Godot, leave that up to me.
- Read the codebase to implement the requested change in the most optimal, efficient, and with the least amount of code addition.
Architecture
Core Components
- main.gd - Main entry point that orchestrates HTML parsing and rendering
- HTMLParser.gd - Parses HTML content into a tree structure of HTMLElement objects
- CSSParser.gd - Parses utility-class-based CSS and applies styles to elements
- StyleManager.gd - Applies parsed styles to Godot UI nodes
- TabContainer.gd - Manages browser tabs with keyboard shortcuts (Ctrl+T, Ctrl+W, Ctrl+Tab)
HTML Element System
HTML elements are mapped to Godot scene files in Scenes/Tags/:
p.tscn,div.tscn,span.tscnfor text contentinput.tscn,button.tscn,select.tscn,textarea.tscnfor form elementsul.tscn,ol.tscn,li.tscnfor listsimg.tscnfor images with network loading supportbr.tscn,separator.tscnfor layout elements
Each has a corresponding .gd script in Scripts/Tags/ that handles initialization and behavior.
Styling System
The project uses a Tailwind-like utility class system:
- Font sizes:
text-xs(12px) throughtext-6xl(60px) - Colors:
text-[#color],bg-[#color]for arbitrary colors; named colors liketext-red-500 - Typography:
font-bold,font-italic,underline,font-mono - Flexbox: Full flexbox support with
flex,justify-center,items-center, etc. - Sizing:
w-[value],h-[value],max-w-[value],min-h-[value] - Spacing:
p-4,px-4,py-2,gap-4 - Border radius:
rounded,rounded-lg,rounded-full,rounded-[12px] - Pseudo-classes:
hover:bg-blue-500,active:bg-red-600
Key Utilities
- Utils/ColorUtils.gd - Color parsing and management
- Utils/SizeUtils.gd - Size/dimension parsing
- Utils/FlexUtils.gd - Flexbox property handling
- Utils/SizingUtils.gd - Size constraint application
- Utils/UtilityClassValidator.gd - Validates utility class syntax
Content System
HTML content is stored in Constants.gd as HTML_CONTENT (with several examples). The parser processes this content, applies styles, and renders it using Godot's UI system with FlexContainer nodes for layout.
Key Features
- Tabbed browsing with keyboard shortcuts
- Network image loading
- Form elements (inputs, selects, textareas, buttons)
- Flexbox layout system
- Utility-class-based styling
- Inline and block element handling
- Custom HTML elements like
<separator>
Current Limitations
- No JavaScript support (planned: Lua scripting)
- Limited to basic HTML tags
<br />elements cause layout spacing issues- No table support yet
- External CSS files not supported
- GIF support planned but not implemented
The codebase follows a clean separation between parsing (HTMLParser), styling (CSSParser/StyleManager), and rendering (main.gd + individual tag scripts).
Lua Documentation
GDLuaU Technical Specification
This document provides a complete technical specification for the GDLuaU plugin. It is intended for developers who want to use the plugin for in-game scripting, modding, or other purposes, and for those who may wish to extend or reimplement its functionality.
Table of Contents
- Overview
- Setup and Initialization
- Core Classes
- LuauVM API Reference
- Data Type Conversions
- Luau Environment Features
- Limitations & Caveats
1. Overview
GDLuaU is a GDExtension that integrates the Luau scripting language into Godot. It is primarily designed for user-generated content, in-game scripting, and modding, rather than as a replacement for GDScript.
The plugin's core philosophy is to expose the comprehensive Luau C API directly within GDScript. This provides a low-level, powerful, and flexible interface for interacting with a Luau virtual machine. It includes features for seamless data marshalling between Godot and Luau, such as pushing and pulling Godot Objects, Arrays, Dictionaries, and other core types.
Key Features:
- A
LuauVMnode that encapsulates alua_State. - Direct bindings to the majority of the Luau 5.1 C API.
- Automatic conversion between Godot's
Varianttypes and Luau's native types. - Support for pushing Godot
Objects (like Nodes and RefCounted) to Luau as userdata. - Support for pushing Godot
Callables to Luau as functions. - A custom
printimplementation in Luau that emits a Godot signal. - A configurable interrupt signal to prevent and handle runaway scripts.
- A built-in
vectorlibrary in Luau for working with Godot's vector types.
2. Setup and Initialization
To use GDLuaU, you must first add a LuauVM node to your scene. This node represents a single, isolated Luau virtual machine.
Once the node is in the scene tree, you need to initialize its environment by loading the standard Luau libraries. This is typically done in the _ready function of a parent script.
Example (GDScript):
extends Node
@onready var vm: LuauVM = $LuauVM
func _ready():
# Load all standard libraries (base, table, string, math, etc.)
vm.open_all_libraries()
# Now the VM is ready to execute code
var result = vm.lua_dostring("print('Hello from Luau!')")
if result != vm.LUA_OK:
# Handle error
var error_message = vm.lua_tostring(-1)
print("Luau Error: ", error_message)
vm.lua_pop(1)
File Structure: The plugin expects a specific file structure within your Godot project, which is set up by default when installed:
project/
└── addons/
└── gdluau/
├── bin/
│ ├── <platform>/
│ │ └── <library_files>
└── gdluau.gdextension
3. Core Classes
The plugin registers three main classes with Godot's ClassDB.
3.1. LuauVM
This is the central class of the plugin. It inherits from Node and represents a single Luau VM instance (lua_State). All interactions with Luau, from running code to manipulating the stack, are done through methods on a LuauVM object.
3.2. LuauFunction
This class represents a reference to a Luau function within GDScript. It inherits from RefCounted. You can obtain a LuauFunction object by calling LuauVM.lua_tofunction() on a function value on the stack. Its primary purpose is to allow you to call Luau functions from GDScript in a safe and convenient way.
GDScript Example:
# Assume a Luau function 'add' has been defined and pushed to the stack
vm.lua_getglobal("add")
var luau_add_func: LuauFunction = vm.lua_tofunction(-1)
vm.lua_pop(1) # Pop the function from the stack
# Now call it from GDScript
var result: LuauFunctionResult = luau_add_func.pcall(5, 7)
if not result.is_error():
print("Result from Luau: ", result.get_tuple()[0]) # Output: 12
3.3. LuauFunctionResult
This RefCounted class acts as a container for the results of a protected call (pcall) made to a LuauFunction. A protected call will not crash the application on an error. Instead, the LuauFunctionResult will indicate whether an error occurred.
- If the call was successful,
is_error()returnsfalse, andget_tuple()returns anArraycontaining all the values returned by the Luau function. - If an error occurred,
is_error()returnstrue, andget_error()returns the error message as aString.
4. LuauVM API Reference
This section details the properties, signals, and methods exposed by the LuauVM class.
4.1. Properties
interrupt_cooldown:float(default:0.1)- Description: The minimum time in seconds between emissions of the
interruptsignal. This is used to prevent the signal from firing too frequently in a tight loop, which could cause performance issues.
- Description: The minimum time in seconds between emissions of the
4.2. Signals
stdout(message: String)- Description: Emitted when the
print()function is called within the Luau environment. The arguments passed toprint()are concatenated and sent as a single string.
- Description: Emitted when the
interrupt()- Description: Emitted periodically during Luau script execution. This signal can be used to terminate runaway scripts (e.g.,
while true do end). You can connect a function to this signal to check for conditions (like a timeout) and calllua_error()to stop the script.
- Description: Emitted periodically during Luau script execution. This signal can be used to terminate runaway scripts (e.g.,
Example: Handling Runaway Scripts
const MAX_EXECUTION_TIME = 2.0
var start_time = 0.0
func _ready():
vm.interrupt.connect(_on_vm_interrupt)
func _on_vm_interrupt():
if Time.get_ticks_msec() / 1000.0 - start_time > MAX_EXECUTION_TIME:
# This will stop the script and trigger an error state
vm.luaL_error("Execution timed out")
func run_script():
start_time = Time.get_ticks_msec() / 1000.0
# This script would otherwise run forever
var result = vm.lua_dostring("while true do end")
if result != vm.LUA_OK:
print("Script stopped: ", vm.lua_tostring(-1))
vm.lua_pop(1)
4.3. Core Methods
These methods provide high-level control over the VM.
open_all_libraries()- Loads all standard Luau libraries into the VM state. This includes
base,coroutine,table,os,string,math,vector,debug,utf8, andbit32.
- Loads all standard Luau libraries into the VM state. This includes
open_libraries(libraries: PackedByteArray)- Loads a specific set of libraries. The
librariesarray should contain integers corresponding to thelua_Libenum (e.g.,[vm.LUA_BASE_LIB, vm.LUA_TABLE_LIB]).
- Loads a specific set of libraries. The
load_string(code: String, chunkname: String = "loadstring") -> int- Compiles a string of Luau code and pushes the resulting function onto the stack. It does not execute the code. Returns a
lua_Statuscode (LUA_OKon success).
- Compiles a string of Luau code and pushes the resulting function onto the stack. It does not execute the code. Returns a
do_string(code: String, chunkname: String = "dostring") -> int- Compiles and immediately executes a string of Luau code. This is equivalent to
load_stringfollowed bylua_pcall. Returns alua_Statuscode (LUA_OKon success). If an error occurs, the error message is pushed onto the stack.
- Compiles and immediately executes a string of Luau code. This is equivalent to
4.4. Data Exchange API (GDLuau Specific)
These functions are custom helpers for moving data between Godot and Luau.
lua_pushvariant(var: Variant): Pushes any supported GodotVariantonto the stack. See Data Type Conversions.lua_pusharray(arr: Array): Pushes a GodotArrayas a new Luau table.lua_pushdictionary(dict: Dictionary): Pushes a GodotDictionaryas a new Luau table.lua_pushobject(object: Object): Pushes a GodotObject(e.g., aNodeorRefCounted) as Luau userdata. The object's lifetime is managed via reference counting forRefCountedtypes.lua_pushcallable(func: Callable, debugname: String = "") -> Error: Pushes a GodotCallableas a Luau function. Note: Lambda (custom) callables are not supported.lua_tovariant(index: int) -> Variant: Pops a value from the stack atindexand converts it to a GodotVariant.lua_toarray(index: int) -> Array: Converts a Luau table atindexinto a GodotArray.lua_todictionary(index: int) -> Dictionary: Converts a Luau table atindexinto a GodotDictionary.lua_tofunction(index: int) -> LuauFunction: Creates aLuauFunctionreference to the Luau function atindex.lua_toobject(index: int) -> Object: Converts Luau userdata atindexback into a GodotObject. Returnsnullif the value is not a valid object userdata.lua_isobject(index: int) -> bool: Checks if the value atindexis a userdata representing a GodotObject.lua_isvalidobject(index: int) -> bool: Checks if the userdata atindexpoints to a valid (not freed) GodotObject.
Example: Pushing and Getting a Node
# GDScript
func _ready():
vm.open_all_libraries()
# Push this node as a global variable 'player' in Luau
vm.lua_pushobject(self)
vm.lua_setglobal("player")
vm.lua_dostring("print(player)")
``````lua
-- LuaU
-- Assuming the above GDScript ran, 'player' is a userdata
-- representing the Godot node.
print(player) -- Outputs something like: [Node:12345]
4.5. Standard Luau API Bindings
The plugin exposes a vast portion of the standard Luau C API as methods on the LuauVM object. The function signatures are adapted for GDScript (e.g., C strings become Godot Strings, integers become int).
For a detailed explanation of what each function does, please refer to the Lua 5.1 Manual, as Luau's API is based on it.
Partial List of Bound Functions:
- Stack Manipulation:
lua_gettop,lua_settop,lua_pushvalue,lua_pop,lua_remove,lua_insert,lua_replace,lua_concat. - Pushing Primitives:
lua_pushnil,lua_pushboolean,lua_pushinteger,lua_pushnumber,lua_pushstring. - Checking Types:
lua_isboolean,lua_isfunction,lua_isnil,lua_isnone,lua_isnumber,lua_isstring,lua_istable,lua_isuserdata,lua_isvector,lua_type,lua_typename. - Accessing Values:
lua_toboolean,lua_tointeger,lua_tonumber,lua_tostring,lua_tovector,lua_objlen. - Table Operations:
lua_newtable,lua_createtable,lua_getfield,lua_setfield,lua_gettable,lua_settable,lua_rawget,lua_rawset,lua_rawgeti,lua_rawseti. - Execution:
lua_call,lua_pcall. - Globals:
lua_getglobal,lua_setglobal. - Metatables:
lua_getmetatable,lua_setmetatable. - References:
lua_ref,lua_unref,lua_getref. (These are aliases forluaL_ref, etc.) - Garbage Collection:
lua_gc.
4.6. Auxiliary Library (luaL_) Bindings
The auxiliary library provides higher-level helper functions.
- Metatable Helpers:
luaL_newmetatable,luaL_getmetatable,luaL_callmeta,luaL_getmetafield,luaL_hasmetatable. - Error Handling and Argument Checking:
luaL_error,luaL_argcheck,luaL_checkany,luaL_checkint,luaL_checknumber,luaL_checkstring,luaL_checkvector,luaL_checkobject,luaL_checktype,luaL_where,luaL_typerror. - Other:
luaL_checkstack,luaL_checkoption.
4.7. Constants
The plugin binds many of Luau's internal constants as enums on the LuauVM class, allowing for type-safe usage in GDScript.
lua_Status:LUA_OK,LUA_YIELD,LUA_ERRRUN,LUA_ERRSYNTAX, etc.lua_Type:LUA_TNIL,LUA_TBOOLEAN,LUA_TNUMBER,LUA_TSTRING,LUA_TTABLE, etc.lua_Lib:LUA_BASE_LIB,LUA_TABLE_LIB,LUA_STRING_LIB, etc.lua_GCOp:LUA_GCCOLLECT,LUA_GCSTOP,LUA_GCRESTART,LUA_GCCOUNT, etc.- Special Indices:
LUA_REGISTRYINDEX,LUA_GLOBALSINDEX. - Other:
LUA_MULTRET,LUA_REFNIL,LUA_NOREF.
5. Data Type Conversions
The following table summarizes how data types are marshaled between GDScript (Variant) and Luau.
| GDScript Type | Luau Type | Notes |
|---|---|---|
Nil |
nil |
|
bool |
boolean |
|
int |
number (integer) |
|
float |
number (double) |
|
String |
string |
|
PackedByteArray |
Not directly supported | |
Array |
table (array-like) |
A new table with integer keys starting from 1. |
Dictionary |
table (dictionary-like) |
Keys and values are converted recursively. |
Vector2, Vector2i |
vector |
Converted to a Luau vector. z and w are 0. |
Vector3, Vector3i |
vector |
Converted to a Luau vector. w is 0. |
Vector4, Vector4i |
vector |
|
Object |
userdata |
A special userdata that holds the object's instance ID. |
Callable |
function |
A C-closure is created in Luau that calls the Godot Callable. |
LuauFunction |
function |
Pushing is not supported; obtained via lua_tofunction. |
6. Luau Environment Features
6.1. Custom print Function
The global print function in Luau is overridden. Instead of printing to the standard console output, it concatenates all its arguments into a single string and emits the stdout signal on the LuauVM node.
LuaU Example:
print("Player health:", 100, "Mana:", 50)
GDScript Receiver:
func _on_vm_stdout(message: String):
# message will be "Player health: 100 Mana: 50"
print("Message from Luau VM: ", message)
6.2. vector Library
GDLuaU includes a custom vector library that is automatically loaded with open_all_libraries. This library allows Luau scripts to create and interact with vector types that are compatible with Godot's Vector2/3/4.
- Constructor:
vector(x: number, y: number, z: number, w: number)- Creates a new vector. You can omit arguments; they will default to 0.
- Indexing: Vectors can be indexed with numbers
1through4to get thex,y,z, andwcomponents respectively. __tostring: Printing a vector will produce a human-readable string (e.g.,"1.0, 2.0, 3.0, 0.0").
LuaU Example:
local pos = vector(10, 25.5) -- Creates a vector(10, 25.5, 0, 0)
local dir = vector(0, -1)
local new_pos = pos + dir -- Vector math is supported
print(new_pos) -- Outputs "10.0, 24.5, 0.0, 0.0"
-- Accessing components
print("X component:", new_pos[1]) -- Outputs "X component: 10.0"```
---
## 7. Limitations & Caveats
* **Performance**: While Luau is highly performant, frequent and heavy data marshalling between Godot and Luau (e.g., converting large arrays/dictionaries every frame) can introduce overhead.
* **Userdata**: The plugin does not expose the raw `userdata` or `lightuserdata` C API. Godot `Object`s are the only supported form of userdata, which are handled automatically.
* **Memory Management**: The `LuauVM` uses Godot's memory functions (`memalloc`, `memfree`). Memory usage can be monitored with `lua_gc(LUA_GCCOUNT, 0)`. Be mindful of creating too many references between Godot and Luau that could lead to memory leaks if not managed correctly.
* **Threading**: A `LuauVM` instance (`lua_State`) is not thread-safe. All interactions with a single VM must be done from the same thread.