mirror of
https://github.com/CompeyDev/lune-packaging.git
synced 2025-01-09 12:19:09 +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
|
||||
|
||||
- Functions such as `print`, `warn`, ... now respect `__tostring` metamethods
|
||||
- Functions such as `print`, `warn`, ... now respect `__tostring` metamethods.
|
||||
|
||||
### 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
|
||||
|
||||
### Fixed
|
||||
|
||||
- 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 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 save instances that contain unsupported attribute types
|
||||
- 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 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 save instances that contain unsupported attribute types.
|
||||
|
||||
## `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 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 `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
|
||||
|
||||
|
@ -47,22 +49,22 @@ This release adds some new features and fixes for the `roblox` built-in.
|
|||
|
||||
### Added
|
||||
|
||||
- Added `GetAttribute`, `GetAttributes` and `SetAttribute` methods for instances
|
||||
- Added support for getting & setting properties that are instance references
|
||||
- Added `GetAttribute`, `GetAttributes` and `SetAttribute` methods for instances.
|
||||
- Added support for getting & setting properties that are instance references.
|
||||
|
||||
### 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 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
|
||||
|
||||
### 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
|
||||
|
||||
|
|
|
@ -1,16 +1,37 @@
|
|||
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;
|
||||
|
||||
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) {
|
||||
add_class_restricted_method(m, CLASS_NAME, "GetService", data_model_get_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.
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ use crate::{
|
|||
|
||||
pub(crate) mod collection_service;
|
||||
pub(crate) mod data_model;
|
||||
pub(crate) mod workspace;
|
||||
|
||||
const PROPERTY_NAME_ATTRIBUTES: &str = "Attributes";
|
||||
const PROPERTY_NAME_TAGS: &str = "Tags";
|
||||
|
@ -34,8 +35,8 @@ static INTERNAL_DOM: Lazy<RwLock<WeakDom>> =
|
|||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Instance {
|
||||
dom_ref: DomRef,
|
||||
class_name: String,
|
||||
pub(crate) dom_ref: DomRef,
|
||||
pub(crate) class_name: String,
|
||||
}
|
||||
|
||||
impl Instance {
|
||||
|
@ -45,7 +46,7 @@ impl Instance {
|
|||
Panics if the instance does not exist in the internal dom,
|
||||
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
|
||||
.try_read()
|
||||
.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.
|
||||
*/
|
||||
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
|
||||
.try_write()
|
||||
.expect("Failed to get write access to document");
|
||||
|
@ -842,6 +843,13 @@ impl 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) {
|
||||
methods.add_meta_method(LuaMetaMethod::ToString, |lua, this, ()| {
|
||||
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 rbx_dom_weak::types::Variant as DomValue;
|
||||
|
||||
use crate::instance::Instance;
|
||||
|
||||
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>(
|
||||
methods: &mut M,
|
||||
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_instance_attributes: "roblox/instance/attributes",
|
||||
roblox_instance_datamodel: "roblox/instance/datamodel",
|
||||
roblox_instance_new: "roblox/instance/new",
|
||||
roblox_instance_properties: "roblox/instance/properties",
|
||||
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_clone: "roblox/instance/methods/Clone",
|
||||
roblox_instance_methods_destroy: "roblox/instance/methods/Destroy",
|
||||
|
|
|
@ -3,20 +3,26 @@ local Instance = roblox.Instance
|
|||
|
||||
local game = Instance.new("DataModel")
|
||||
|
||||
assert(game:FindService("Workspace") == nil)
|
||||
assert(game:GetService("Workspace") ~= nil)
|
||||
assert(game:FindService("Workspace") ~= nil)
|
||||
-- Workspace should always exist as a "Workspace" property, or be created when accessed
|
||||
|
||||
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:GetService("CSGDictionaryService") ~= nil)
|
||||
assert(game:FindService("CSGDictionaryService") ~= nil)
|
||||
|
||||
-- Service names should be strict and not allow weird characters or substrings
|
||||
|
||||
assert(not pcall(function()
|
||||
game:GetService("wrorokspacey")
|
||||
end))
|
||||
|
||||
assert(not pcall(function()
|
||||
game:GetService("work-space")
|
||||
game:GetService("Work-space")
|
||||
end))
|
||||
|
||||
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