Skip to content

Commit

Permalink
Add shortcut for Resource properties
Browse files Browse the repository at this point in the history
  • Loading branch information
gilzoide committed Sep 7, 2021
1 parent 052a4a3 commit 8afeab1
Show file tree
Hide file tree
Showing 6 changed files with 145 additions and 64 deletions.
83 changes: 45 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,12 @@ as a scripting language in [Godot](https://godotengine.org/).
Being a GDNative library, recompiling the engine is not required, so anyone
with a built release copied to their project can use it.
Being a PluginScript language, Lua can seamlessly communicate with scripts
written in GDScript/C#/Visual Script and vice-versa.
written in GDScript / C# / Visual Script and vice-versa.
This way, one can use the language that best suits the implementation for each
script and all of them can understand each other.


## Documentation
The API is documented using [LDoc](https://stevedonovan.github.io/ldoc/manual/doc.md.html)
and is available online at [github pages](https://gilzoide.github.io/godot-lua-pluginscript/topics/README.md.html).

Generate the documentation with the following command:

# make docs
Currently, only LuaJIT is supported, since the implementation is based on its
[FFI](https://luajit.org/ext_ffi.html) library.


## Installing
Expand All @@ -25,14 +21,6 @@ Make sure the `lua_pluginscript.gdnlib` file is located at the
`res://addons/godot-lua-pluginscript` folder.


## Articles

1. [Designing Godot Lua PluginScript](blog/1-design-en.md)
2. [Implementing the library's skeleton](blog/2-infrastructure-en.md)
3. [Integrating LuaJIT and FFI](blog/3-luajit-callbacks-en.md)
4. Initializing and finalizing scripts (TODO)


## Goals

- Provide support for Lua as a scripting language in Godot in a way that does
Expand All @@ -41,7 +29,7 @@ Make sure the `lua_pluginscript.gdnlib` file is located at the
like GDScript, Visual Script and C#, in an idiomatic way
- Simple script description interface that doesn't need `require`ing anything
- Support for LuaJIT and Lua 5.2+
- Support paths relative to `res://*` and exported game executable path for
- Support paths relative to `res://*` and exported game/app executable path for
`require`ing Lua modules
- Have a simple build process, where anyone with the cloned source code and
installed build system + toolchain can build the project in a single step
Expand All @@ -53,6 +41,23 @@ Make sure the `lua_pluginscript.gdnlib` file is located at the
- Support multithreading on the Lua side


## Documentation
The API is documented using [LDoc](https://stevedonovan.github.io/ldoc/manual/doc.md.html)
and is available online at [github pages](https://gilzoide.github.io/godot-lua-pluginscript/topics/README.md.html).

Documentation may be generated with the following command:

# make docs


## Articles

1. [Designing Godot Lua PluginScript](https://github.com/gilzoide/godot-lua-pluginscript/blob/main/blog/1-design-en.md)
2. [Implementing the library's skeleton](https://github.com/gilzoide/godot-lua-pluginscript/blob/main/blog/2-infrastructure-en.md)
3. [Integrating LuaJIT and FFI](https://github.com/gilzoide/godot-lua-pluginscript/blob/main/blog/3-luajit-callbacks-en.md)
4. Initializing and finalizing scripts (TODO)


## Script example

This is an example of how a Lua script looks like. There are comments regarding
Expand Down Expand Up @@ -81,37 +86,32 @@ MyClass.some_prop = 42
-- The `property` function adds metadata to defined properties,
-- like setter and getter functions
MyClass.some_prop_with_details = property {
-- [1] or ["default"] or ["default_value"] = property default value
-- ["default_value"] or ["default"] or [1] = property default value
5,
-- [2] or ["type"] = variant type, optional, inferred from default value
-- ["type"] or [2] = variant type, optional, inferred from default value
-- All Godot variant type names are defined globally as written in
-- GDScript, like bool, int, float, String, Array, Vector2, etc...
-- Notice that Lua <= 5.2 does not differentiate integers from float
-- numbers, so we should always specify `int` where appropriate
-- or use `int(5)` in the default value instead
type = int,
-- ["set"] or ["setter"] = setter function, optional
set = function(self, value)
self.some_prop_with_details = value
-- Indexing `self` with keys undefined in script will search base
-- class for methods and properties
self:emit_signal("something_happened_with_args", "some_prop_with_details", value)
end,
-- ["get"] or ["getter"] = getter function, optional
-- ["get"] or ["getter"] = getter function or method name, optional
get = function(self)
return self.some_prop_with_details
end,
-- ["usage"] = property usage, from enum godot_property_usage_flags
-- optional, default to GD.PROPERTY_USAGE_DEFAULT
usage = GD.PROPERTY_USAGE_DEFAULT,
-- ["hint"] = property hint, from enum godot_property_hint
-- optional, default to GD.PROPERTY_HINT_NONE
hint = GD.PROPERTY_HINT_RANGE,
-- ["set"] or ["setter"] = setter function or method name, optional
set = 'set_some_prop_with_details',
-- ["usage"] = property usage, from `enum godot_property_usage_flags`
-- optional, default to `PropertyUsage.DEFAULT`
usage = PropertyUsage.DEFAULT,
-- ["hint"] = property hint, from `enum godot_property_hint`
-- optional, default to `PropertyHint.NONE`
hint = PropertyHint.RANGE,
-- ["hint_string"] = property hint text, only required for some hints
hint_string = '1,10',
-- ["rset_mode"] = property remote set mode, from enum godot_method_rpc_mode
-- optional, default to GD.RPC_MODE_DISABLED
rset_mode = GD.RPC_MODE_MASTER,
-- ["rset_mode"] = property remote set mode, from `enum godot_method_rpc_mode`
-- optional, default to `RPCMode.DISABLED`
rset_mode = RPCMode.MASTER,
}

-- Functions defined in table are public methods
Expand All @@ -121,7 +121,14 @@ function MyClass:_ready() -- `function t:f(...)` is an alias for `function t.f(
print("MyClass instance is ready! Running on a " .. os_name .. " system")
end

function MyClass:some_prop_doubled()
function MyClass:set_some_prop_with_details(value)
self.some_prop_with_details = value
-- Indexing `self` with keys undefined in script will search base
-- class for methods and properties
self:emit_signal("something_happened_with_args", "some_prop_with_details", value)
end

function MyClass:get_some_prop_doubled()
return self.some_prop * 2
end

Expand Down
23 changes: 23 additions & 0 deletions src/godot_class.lua
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,19 @@ local class_methods = {
Object_call(obj, '_init', ...)
return obj
end,
--- Returns whether this Class inherits a `parent` Class.
-- @function inherits
-- @param other Other class name
-- @treturn bool
inherits = function(self, parent)
return ClassDB:is_parent_class(self.class_name, parent)
end,
--- Returns the parent class.
-- @function get_parent_class
-- @treturn String
get_parent_class = function(self)
return ClassDB:get_parent_class(self.class_name)
end,
}
local ClassWrapper = {
new = function(self, class_name)
Expand Down Expand Up @@ -114,8 +127,18 @@ local ClassWrapper = {
return constant
end
end,
--- Returns `class_name`
-- @function __tostring
-- @treturn string
__tostring = function(self)
return self.class_name
end,
}

local function is_class_wrapper(v)
return getmetatable(v) == ClassWrapper
end

--- MethodBind metatype, wrapper for `godot_method_bind`.
-- These are returned by `ClassWrapper:__index` and GDNative's `godot_method_bind_get_method`.
-- @type MethodBind
Expand Down
22 changes: 11 additions & 11 deletions src/godot_enums.lua
Original file line number Diff line number Diff line change
Expand Up @@ -241,19 +241,19 @@ for k, v in pairs(RPCMode) do RPCMode[v] = k end
--- Enum `godot_property_hint`, mapping UPPER_CASED names to ordinal values and vice-versa.
-- @table PropertyHint
-- @field NONE None
-- @field RANGE Range
-- @field EXP_RANGE Exp Range
-- @field ENUM Enum
-- @field RANGE Range (hint_string = `"min,max,step,slider"`; step and slider are optional)
-- @field EXP_RANGE Exp Range (hint_string = `"min,max,step"`; exponential edit)
-- @field ENUM Enum (hint_string = `"val1,val2,val3,etc"`)
-- @field EXP_EASING Exp Easing
-- @field LENGTH Length
-- @field SPRITE_FRAME Sprite Frame
-- @field KEY_ACCEL Key Accel
-- @field FLAGS Flags
-- @field LENGTH Length (hint_string = `"length"`; as integer)
-- @field SPRITE_FRAME Sprite Frame (Obsolete)
-- @field KEY_ACCEL Key Accel (hint_string = `"length"`; as integer)
-- @field FLAGS Flags (hint_string = `"flag1,flag2,etc"`; as bit flags)
-- @field LAYERS_2D_RENDER Layers 2D Render
-- @field LAYERS_2D_PHYSICS Layers 2D Physics
-- @field LAYERS_3D_RENDER Layers 3D Render
-- @field LAYERS_3D_PHYSICS Layers 3D Physics
-- @field FILE File
-- @field FILE File (optional hint_string: file filter, e.g. `"\*.png,\*.wav"`)
-- @field DIR Dir
-- @field GLOBAL_FILE Global File
-- @field GLOBAL_DIR Global Dir
Expand All @@ -264,7 +264,7 @@ for k, v in pairs(RPCMode) do RPCMode[v] = k end
-- @field IMAGE_COMPRESS_LOSSY Image Compress Lossy
-- @field IMAGE_COMPRESS_LOSSLESS Image Compress Lossless
-- @field OBJECT_ID Object Id
-- @field TYPE_STRING Type String
-- @field TYPE_STRING Type String (hint_string = `"base class name"`)
-- @field NODE_PATH_TO_EDITED_NODE Node Path To Edited Node
-- @field METHOD_OF_VARIANT_TYPE Method Of Variant Type
-- @field METHOD_OF_BASE_TYPE Method Of Base Type
Expand Down Expand Up @@ -323,8 +323,8 @@ for k, v in pairs(PropertyHint) do PropertyHint[v] = k end
-- @field INTERNATIONALIZED Internationalized
-- @field GROUP Group
-- @field CATEGORY Category
-- @field STORE_IF_NONZERO Store If Nonzero
-- @field STORE_IF_NONONE Store If Nonone
-- @field STORE_IF_NONZERO Store If Nonzero (Obsolete)
-- @field STORE_IF_NONONE Store If Nonone (Obsolete)
-- @field NO_INSTANCE_STATE No Instance State
-- @field RESTART_IF_CHANGED Restart If Changed
-- @field SCRIPT_VARIABLE Script Variable
Expand Down
2 changes: 1 addition & 1 deletion src/godot_string_name.lua
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
-- IN THE SOFTWARE.

--- StringName metatype, wrapper for `godot_string_name`.
-- Construct using the idiom `StringName(...)`, which calls `__new`.
-- Construct using the idiom `StringName(name)`, which calls `__new`.
-- @classmod StringName

local methods = {
Expand Down
7 changes: 0 additions & 7 deletions src/pluginscript_callbacks.lua
Original file line number Diff line number Diff line change
Expand Up @@ -128,17 +128,10 @@ clib.lps_script_init_cb = wrap_callback(function(manifest, path, source, err)
prop.name = String(k)
-- Maintain default value directly for __indexing
metadata[k] = default_value
-- TODO: support strings for get/set
if get then
if is_a_string(get) then
get = MethodBindByName:new(get)
end
getter[k] = get
end
if set then
if is_a_string(set) then
set = MethodBindByName:new(set)
end
setter[k] = set
end
manifest.properties:append(prop)
Expand Down
72 changes: 65 additions & 7 deletions src/pluginscript_class_metadata.lua
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@
-- FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-- IN THE SOFTWARE.

-- Map names and ctypes to godot_variant_type
--- Scripts metadata.
-- This includes properties, signals and methods.
-- @module script_metadata

-- Map names and types to godot_variant_type
local property_types = {
bool = VariantType.Bool, [bool] = VariantType.Bool,
int = VariantType.Int, [int] = VariantType.Int,
Expand Down Expand Up @@ -85,23 +89,69 @@ local function property_to_dictionary(prop)
default_value = prop
dict.type = get_property_type(prop)
else
default_value = prop[1] or prop.default or prop.default_value
local explicit_type = prop[2] or prop.type
dict.type = property_types[explicit_type] or explicit_type or get_property_type(default_value)
dict.hint = prop.hint
dict.hint_string = prop.hint_string
default_value = prop.default_value or prop.default or prop[1]
local explicit_type = prop.type or prop[2]
if is_class_wrapper(explicit_type) and explicit_type:inherits('Resource') then
dict.type = VariantType.Object
dict.hint = PropertyHint.RESOURCE_TYPE
dict.hint_string = explicit_type.class_name
else
dict.type = property_types[explicit_type] or explicit_type or get_property_type(default_value)
dict.hint = prop.hint
dict.hint_string = prop.hint_string
end
dict.usage = prop.usage
dict.rset_mode = prop.rset_mode
get = prop.get or prop.getter
if is_a_string(get) then
get = MethodBindByName:new(get)
end
set = prop.set or prop.setter
if is_a_string(set) then
set = MethodBindByName:new(set)
end
end
dict.default_value = default_value
return dict, default_value, get, set
end

--- Adds `metadata` to a property.
-- If `metadata` is not a table, creates a table with this value as `default_value`.
-- The `metadata` table may include the following fields:
--
-- * `default_value` or `default` or `1`: default property value, returned when
-- Object has no other value set for it.
-- * (*optional*) `type` or `2`: property type. If absent, it is inferred from `default_value`.
-- May be a `Enumerations.VariantType` (`VariantType.Vector2`), the type directly (`Vector2`),
-- or a Resource class (`AudioStream`)
-- * (*optional*) `get` or `getter`: getter function. May be a string with the method name
-- to be called or any callable value, like functions and tables with a `__call`
-- metamethod.
-- * (*optional*) `set` or `setter`: setter function. May be a string with the method name
-- to be called or any callable value, like functions and tables with a `__call`
-- metamethod.
-- * (*optional*) `hint`: one of `Enumerations.PropertyHint`. Default to `PropertyHint.NONE`.
-- * (*optional*) `hint_string`: the hint text, required for some hints like `RANGE`.
-- * (*optional*) `usage`: one of `Enumerations.PropertyUsage`. Default to `PropertyUsage.DEFAULT`.
-- * (*optional*) `rset_mode`: one of `Enumerations.RPCMode`. Default to `RPCMode.DISABLED`.
--
-- TODO: accept hints directly (`range = '1,10'`; `enum = 'value1,value2,value3'`; `file = '*.png'`, etc...).
-- @usage
-- MyClass.some_prop = property(42)
-- MyClass.some_prop_with_metadata = property {
-- type = int,
-- set = function(self, value)
-- self.some_prop_with_metadata = value
-- self:emit('some_prop_with_metadata_changed', value)
-- end,
-- hint = PropertyHint.RANGE,
-- hint_text = '1,100',
-- }
-- @treturn table
-- @see lps_coroutine.lua
function property(metadata)
if type(metadata) ~= 'table' then
metadata = { metadata }
metadata = { default_value = metadata }
end
return setmetatable(metadata, Property)
end
Expand All @@ -122,6 +172,14 @@ local function signal_to_dictionary(sig)
return dict
end

--- Create a signal table.
-- This is only useful for declaring scripts' signals.
-- @usage
-- MyClass.something_happened = signal()
-- MyClass.something_happened_with_args = signal('arg1', 'arg2', 'etc')
-- @param ... Signal argument names
-- @treturn table
-- @see lps_coroutine.lua
function signal(...)
return setmetatable({ ... }, Signal)
end
Expand Down

0 comments on commit 8afeab1

Please sign in to comment.