mirror of
https://github.com/CompeyDev/lune-packaging.git
synced 2025-01-10 04:39:08 +00:00
Fix access of special instance link properties on some classes
This commit is contained in:
parent
9b568aa8ec
commit
03680eccc6
8 changed files with 192 additions and 24 deletions
30
CHANGELOG.md
30
CHANGELOG.md
|
@ -12,21 +12,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Functions such as `print`, `warn`, ... now respect `__tostring` metamethods
|
- Functions such as `print`, `warn`, ... now respect `__tostring` metamethods.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fixed issues with CFrame math operations
|
- Fixed access of roblox instance properties such as `Workspace.Terrain`, `game.Workspace` that are actually links to child instances. <br />
|
||||||
|
These properties are always guaranteed to exist, and they are not always properly set, meaning they must be found through an internal lookup.
|
||||||
|
- Fixed issues with CFrame math operations.
|
||||||
|
|
||||||
## `0.6.4` - March 26th, 2023
|
## `0.6.4` - March 26th, 2023
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fixed instances with attributes not saving if they contain integer attributes
|
- Fixed instances with attributes not saving if they contain integer attributes.
|
||||||
- Fixed attributes not being set properly if the instance has an empty attributes property
|
- Fixed attributes not being set properly if the instance has an empty attributes property.
|
||||||
- Fixed error messages for reading & writing roblox files not containing the full error message
|
- Fixed error messages for reading & writing roblox files not containing the full error message.
|
||||||
- Fixed crash when trying to access an instance reference property that points to a destroyed instance
|
- Fixed crash when trying to access an instance reference property that points to a destroyed instance.
|
||||||
- Fixed crash when trying to save instances that contain unsupported attribute types
|
- Fixed crash when trying to save instances that contain unsupported attribute types.
|
||||||
|
|
||||||
## `0.6.3` - March 26th, 2023
|
## `0.6.3` - March 26th, 2023
|
||||||
|
|
||||||
|
@ -37,9 +39,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fixed accessing a destroyed instance printing an error message even if placed inside a pcall
|
- Fixed accessing a destroyed instance printing an error message even if placed inside a pcall.
|
||||||
- Fixed cloned instances not having correct instance reference properties set (`ObjectValue.Value`, `Motor6D.Part0`, ...)
|
- Fixed cloned instances not having correct instance reference properties set (`ObjectValue.Value`, `Motor6D.Part0`, ...)
|
||||||
- Fixed `Instance::GetDescendants` returning the same thing as `Instance::GetChildren` (oops)
|
- Fixed `Instance::GetDescendants` returning the same thing as `Instance::GetChildren`.
|
||||||
|
|
||||||
## `0.6.2` - March 25th, 2023
|
## `0.6.2` - March 25th, 2023
|
||||||
|
|
||||||
|
@ -47,22 +49,22 @@ This release adds some new features and fixes for the `roblox` built-in.
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Added `GetAttribute`, `GetAttributes` and `SetAttribute` methods for instances
|
- Added `GetAttribute`, `GetAttributes` and `SetAttribute` methods for instances.
|
||||||
- Added support for getting & setting properties that are instance references
|
- Added support for getting & setting properties that are instance references.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Improved handling of optional property types such as optional cframes & default physical properties
|
- Improved handling of optional property types such as optional cframes & default physical properties.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fixed handling of instance properties that are serialized as binary strings
|
- Fixed handling of instance properties that are serialized as binary strings.
|
||||||
|
|
||||||
## `0.6.1` - March 22nd, 2023
|
## `0.6.1` - March 22nd, 2023
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fixed `writePlaceFile` and `writeModelFile` in the new `roblox` built-in making mysterious "ROOT" instances
|
- Fixed `writePlaceFile` and `writeModelFile` in the new `roblox` built-in making mysterious "ROOT" instances.
|
||||||
|
|
||||||
## `0.6.0` - March 22nd, 2023
|
## `0.6.0` - March 22nd, 2023
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,37 @@
|
||||||
use mlua::prelude::*;
|
use mlua::prelude::*;
|
||||||
|
|
||||||
use crate::shared::{classes::add_class_restricted_method, instance::class_is_a_service};
|
use crate::shared::{
|
||||||
|
classes::{
|
||||||
|
add_class_restricted_getter, add_class_restricted_method,
|
||||||
|
get_or_create_property_ref_instance,
|
||||||
|
},
|
||||||
|
instance::class_is_a_service,
|
||||||
|
};
|
||||||
|
|
||||||
use super::Instance;
|
use super::Instance;
|
||||||
|
|
||||||
pub const CLASS_NAME: &str = "DataModel";
|
pub const CLASS_NAME: &str = "DataModel";
|
||||||
|
|
||||||
|
pub fn add_fields<'lua, M: LuaUserDataFields<'lua, Instance>>(m: &mut M) {
|
||||||
|
add_class_restricted_getter(m, CLASS_NAME, "Workspace", data_model_get_workspace);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn add_methods<'lua, M: LuaUserDataMethods<'lua, Instance>>(m: &mut M) {
|
pub fn add_methods<'lua, M: LuaUserDataMethods<'lua, Instance>>(m: &mut M) {
|
||||||
add_class_restricted_method(m, CLASS_NAME, "GetService", data_model_get_service);
|
add_class_restricted_method(m, CLASS_NAME, "GetService", data_model_get_service);
|
||||||
add_class_restricted_method(m, CLASS_NAME, "FindService", data_model_find_service);
|
add_class_restricted_method(m, CLASS_NAME, "FindService", data_model_find_service);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Get the workspace parented under this datamodel, or create it if it doesn't exist.
|
||||||
|
|
||||||
|
### See Also
|
||||||
|
* [`Terrain`](https://create.roblox.com/docs/reference/engine/classes/Workspace#Terrain)
|
||||||
|
on the Roblox Developer Hub
|
||||||
|
*/
|
||||||
|
fn data_model_get_workspace(_: &Lua, this: &Instance) -> LuaResult<Instance> {
|
||||||
|
get_or_create_property_ref_instance(this, "Workspace", "Workspace")
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Gets or creates a service for this DataModel.
|
Gets or creates a service for this DataModel.
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ use crate::{
|
||||||
|
|
||||||
pub(crate) mod collection_service;
|
pub(crate) mod collection_service;
|
||||||
pub(crate) mod data_model;
|
pub(crate) mod data_model;
|
||||||
|
pub(crate) mod workspace;
|
||||||
|
|
||||||
const PROPERTY_NAME_ATTRIBUTES: &str = "Attributes";
|
const PROPERTY_NAME_ATTRIBUTES: &str = "Attributes";
|
||||||
const PROPERTY_NAME_TAGS: &str = "Tags";
|
const PROPERTY_NAME_TAGS: &str = "Tags";
|
||||||
|
@ -34,8 +35,8 @@ static INTERNAL_DOM: Lazy<RwLock<WeakDom>> =
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Instance {
|
pub struct Instance {
|
||||||
dom_ref: DomRef,
|
pub(crate) dom_ref: DomRef,
|
||||||
class_name: String,
|
pub(crate) class_name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Instance {
|
impl Instance {
|
||||||
|
@ -45,7 +46,7 @@ impl Instance {
|
||||||
Panics if the instance does not exist in the internal dom,
|
Panics if the instance does not exist in the internal dom,
|
||||||
or if the given dom object ref points to the dom root.
|
or if the given dom object ref points to the dom root.
|
||||||
*/
|
*/
|
||||||
fn new(dom_ref: DomRef) -> Self {
|
pub(crate) fn new(dom_ref: DomRef) -> Self {
|
||||||
let dom = INTERNAL_DOM
|
let dom = INTERNAL_DOM
|
||||||
.try_read()
|
.try_read()
|
||||||
.expect("Failed to get read access to document");
|
.expect("Failed to get read access to document");
|
||||||
|
@ -93,7 +94,7 @@ impl Instance {
|
||||||
|
|
||||||
An orphaned instance is an instance at the root of a weak dom.
|
An orphaned instance is an instance at the root of a weak dom.
|
||||||
*/
|
*/
|
||||||
fn new_orphaned(class_name: impl AsRef<str>) -> Self {
|
pub(crate) fn new_orphaned(class_name: impl AsRef<str>) -> Self {
|
||||||
let mut dom = INTERNAL_DOM
|
let mut dom = INTERNAL_DOM
|
||||||
.try_write()
|
.try_write()
|
||||||
.expect("Failed to get write access to document");
|
.expect("Failed to get write access to document");
|
||||||
|
@ -842,6 +843,13 @@ impl Instance {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LuaUserData for Instance {
|
impl LuaUserData for Instance {
|
||||||
|
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
|
||||||
|
// Here we add inheritance-like behavior for instances by creating
|
||||||
|
// fields that are restricted to specific classnames / base classes
|
||||||
|
data_model::add_fields(fields);
|
||||||
|
workspace::add_fields(fields);
|
||||||
|
}
|
||||||
|
|
||||||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||||
methods.add_meta_method(LuaMetaMethod::ToString, |lua, this, ()| {
|
methods.add_meta_method(LuaMetaMethod::ToString, |lua, this, ()| {
|
||||||
this.ensure_not_destroyed()?;
|
this.ensure_not_destroyed()?;
|
||||||
|
|
34
packages/lib-roblox/src/instance/workspace.rs
Normal file
34
packages/lib-roblox/src/instance/workspace.rs
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
use mlua::prelude::*;
|
||||||
|
|
||||||
|
use crate::shared::classes::{add_class_restricted_getter, get_or_create_property_ref_instance};
|
||||||
|
|
||||||
|
use super::Instance;
|
||||||
|
|
||||||
|
pub const CLASS_NAME: &str = "Workspace";
|
||||||
|
|
||||||
|
pub fn add_fields<'lua, M: LuaUserDataFields<'lua, Instance>>(m: &mut M) {
|
||||||
|
add_class_restricted_getter(m, CLASS_NAME, "Terrain", workspace_get_terrain);
|
||||||
|
add_class_restricted_getter(m, CLASS_NAME, "CurrentCamera", workspace_get_camera);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Get the terrain parented under this workspace, or create it if it doesn't exist.
|
||||||
|
|
||||||
|
### See Also
|
||||||
|
* [`Terrain`](https://create.roblox.com/docs/reference/engine/classes/Workspace#Terrain)
|
||||||
|
on the Roblox Developer Hub
|
||||||
|
*/
|
||||||
|
fn workspace_get_terrain(_: &Lua, this: &Instance) -> LuaResult<Instance> {
|
||||||
|
get_or_create_property_ref_instance(this, "Terrain", "Terrain")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Get the camera parented under this workspace, or create it if it doesn't exist.
|
||||||
|
|
||||||
|
### See Also
|
||||||
|
* [`CurrentCamera`](https://create.roblox.com/docs/reference/engine/classes/Workspace#CurrentCamera)
|
||||||
|
on the Roblox Developer Hub
|
||||||
|
*/
|
||||||
|
fn workspace_get_camera(_: &Lua, this: &Instance) -> LuaResult<Instance> {
|
||||||
|
get_or_create_property_ref_instance(this, "CurrentCamera", "Camera")
|
||||||
|
}
|
|
@ -1,9 +1,54 @@
|
||||||
use mlua::prelude::*;
|
use mlua::prelude::*;
|
||||||
|
|
||||||
|
use rbx_dom_weak::types::Variant as DomValue;
|
||||||
|
|
||||||
use crate::instance::Instance;
|
use crate::instance::Instance;
|
||||||
|
|
||||||
use super::instance::class_is_a;
|
use super::instance::class_is_a;
|
||||||
|
|
||||||
|
pub(crate) fn add_class_restricted_getter<'lua, F: LuaUserDataFields<'lua, Instance>, R, G>(
|
||||||
|
fields: &mut F,
|
||||||
|
class_name: &'static str,
|
||||||
|
field_name: &'static str,
|
||||||
|
field_getter: G,
|
||||||
|
) where
|
||||||
|
R: ToLua<'lua>,
|
||||||
|
G: 'static + Fn(&'lua Lua, &Instance) -> LuaResult<R>,
|
||||||
|
{
|
||||||
|
fields.add_field_method_get(field_name, move |lua, this| {
|
||||||
|
if class_is_a(this.get_class_name(), class_name).unwrap_or(false) {
|
||||||
|
field_getter(lua, this)
|
||||||
|
} else {
|
||||||
|
Err(LuaError::RuntimeError(format!(
|
||||||
|
"{} is not a valid member of {}",
|
||||||
|
field_name, class_name
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) fn add_class_restricted_setter<'lua, F: LuaUserDataFields<'lua, Instance>, A, G>(
|
||||||
|
fields: &mut F,
|
||||||
|
class_name: &'static str,
|
||||||
|
field_name: &'static str,
|
||||||
|
field_getter: G,
|
||||||
|
) where
|
||||||
|
A: FromLua<'lua>,
|
||||||
|
G: 'static + Fn(&'lua Lua, &Instance, A) -> LuaResult<()>,
|
||||||
|
{
|
||||||
|
fields.add_field_method_set(field_name, move |lua, this, value| {
|
||||||
|
if class_is_a(this.get_class_name(), class_name).unwrap_or(false) {
|
||||||
|
field_getter(lua, this, value)
|
||||||
|
} else {
|
||||||
|
Err(LuaError::RuntimeError(format!(
|
||||||
|
"{} is not a valid member of {}",
|
||||||
|
field_name, class_name
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn add_class_restricted_method<'lua, M: LuaUserDataMethods<'lua, Instance>, A, R, F>(
|
pub(crate) fn add_class_restricted_method<'lua, M: LuaUserDataMethods<'lua, Instance>, A, R, F>(
|
||||||
methods: &mut M,
|
methods: &mut M,
|
||||||
class_name: &'static str,
|
class_name: &'static str,
|
||||||
|
@ -54,3 +99,36 @@ pub(crate) fn add_class_restricted_method_mut<
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Gets or creates the instance child with the given reference prop name and class name.
|
||||||
|
|
||||||
|
Note that the class name here must be an exact match, it is not checked using IsA.
|
||||||
|
|
||||||
|
The instance may be in one of several states but this function will guarantee that the
|
||||||
|
property reference is correct and that the instance exists after it has been called:
|
||||||
|
|
||||||
|
1. Instance exists as property ref - just return it
|
||||||
|
2. Instance exists under workspace but not as a property ref - save it and return it
|
||||||
|
3. Instance does not exist - create it, save it, then return it
|
||||||
|
*/
|
||||||
|
pub(crate) fn get_or_create_property_ref_instance(
|
||||||
|
this: &Instance,
|
||||||
|
prop_name: &'static str,
|
||||||
|
class_name: &'static str,
|
||||||
|
) -> LuaResult<Instance> {
|
||||||
|
if let Some(DomValue::Ref(inst_ref)) = this.get_property(prop_name) {
|
||||||
|
if let Some(inst) = Instance::new_opt(inst_ref) {
|
||||||
|
return Ok(inst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(inst) = this.find_child(|child| child.class == class_name) {
|
||||||
|
this.set_property(prop_name, DomValue::Ref(inst.dom_ref));
|
||||||
|
Ok(inst)
|
||||||
|
} else {
|
||||||
|
let inst = Instance::new_orphaned(class_name);
|
||||||
|
inst.set_parent(Some(this.clone()));
|
||||||
|
this.set_property(prop_name, DomValue::Ref(inst.dom_ref));
|
||||||
|
Ok(inst)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -123,11 +123,13 @@ create_tests! {
|
||||||
roblox_files_write_place: "roblox/files/writePlaceFile",
|
roblox_files_write_place: "roblox/files/writePlaceFile",
|
||||||
|
|
||||||
roblox_instance_attributes: "roblox/instance/attributes",
|
roblox_instance_attributes: "roblox/instance/attributes",
|
||||||
roblox_instance_datamodel: "roblox/instance/datamodel",
|
|
||||||
roblox_instance_new: "roblox/instance/new",
|
roblox_instance_new: "roblox/instance/new",
|
||||||
roblox_instance_properties: "roblox/instance/properties",
|
roblox_instance_properties: "roblox/instance/properties",
|
||||||
roblox_instance_tags: "roblox/instance/tags",
|
roblox_instance_tags: "roblox/instance/tags",
|
||||||
|
|
||||||
|
roblox_instance_classes_data_model: "roblox/instance/classes/DataModel",
|
||||||
|
roblox_instance_classes_workspace: "roblox/instance/classes/Workspace",
|
||||||
|
|
||||||
roblox_instance_methods_clear_all_children: "roblox/instance/methods/ClearAllChildren",
|
roblox_instance_methods_clear_all_children: "roblox/instance/methods/ClearAllChildren",
|
||||||
roblox_instance_methods_clone: "roblox/instance/methods/Clone",
|
roblox_instance_methods_clone: "roblox/instance/methods/Clone",
|
||||||
roblox_instance_methods_destroy: "roblox/instance/methods/Destroy",
|
roblox_instance_methods_destroy: "roblox/instance/methods/Destroy",
|
||||||
|
|
|
@ -3,20 +3,26 @@ local Instance = roblox.Instance
|
||||||
|
|
||||||
local game = Instance.new("DataModel")
|
local game = Instance.new("DataModel")
|
||||||
|
|
||||||
assert(game:FindService("Workspace") == nil)
|
-- Workspace should always exist as a "Workspace" property, or be created when accessed
|
||||||
assert(game:GetService("Workspace") ~= nil)
|
|
||||||
assert(game:FindService("Workspace") ~= nil)
|
assert(game.Workspace ~= nil)
|
||||||
|
assert(game.Workspace:IsA("Workspace"))
|
||||||
|
assert(game.Workspace == game:FindFirstChildOfClass("Workspace"))
|
||||||
|
|
||||||
|
-- GetService and FindService should work, GetService should create services that don't exist
|
||||||
|
|
||||||
assert(game:FindService("CSGDictionaryService") == nil)
|
assert(game:FindService("CSGDictionaryService") == nil)
|
||||||
assert(game:GetService("CSGDictionaryService") ~= nil)
|
assert(game:GetService("CSGDictionaryService") ~= nil)
|
||||||
assert(game:FindService("CSGDictionaryService") ~= nil)
|
assert(game:FindService("CSGDictionaryService") ~= nil)
|
||||||
|
|
||||||
|
-- Service names should be strict and not allow weird characters or substrings
|
||||||
|
|
||||||
assert(not pcall(function()
|
assert(not pcall(function()
|
||||||
game:GetService("wrorokspacey")
|
game:GetService("wrorokspacey")
|
||||||
end))
|
end))
|
||||||
|
|
||||||
assert(not pcall(function()
|
assert(not pcall(function()
|
||||||
game:GetService("work-space")
|
game:GetService("Work-space")
|
||||||
end))
|
end))
|
||||||
|
|
||||||
assert(not pcall(function()
|
assert(not pcall(function()
|
17
tests/roblox/instance/classes/Workspace.luau
Normal file
17
tests/roblox/instance/classes/Workspace.luau
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
local roblox = require("@lune/roblox") :: any
|
||||||
|
local Instance = roblox.Instance
|
||||||
|
|
||||||
|
local game = Instance.new("DataModel")
|
||||||
|
local workspace = game:GetService("Workspace")
|
||||||
|
|
||||||
|
-- Terrain should always exist as a "Terrain" property, or be created when accessed
|
||||||
|
|
||||||
|
assert(workspace.Terrain ~= nil)
|
||||||
|
assert(workspace.Terrain:IsA("Terrain"))
|
||||||
|
assert(workspace.Terrain == workspace:FindFirstChildOfClass("Terrain"))
|
||||||
|
|
||||||
|
-- Camera should always exist as a "CurrentCamera" property, or be created when accessed
|
||||||
|
|
||||||
|
assert(workspace.CurrentCamera ~= nil)
|
||||||
|
assert(workspace.CurrentCamera:IsA("Camera"))
|
||||||
|
assert(workspace.CurrentCamera == workspace:FindFirstChildOfClass("Camera"))
|
Loading…
Reference in a new issue