mirror of
https://github.com/lune-org/lune.git
synced 2024-12-12 04:50:36 +00:00
Migrate roblox builtin functions for place & model files to more flexible APIs
This commit is contained in:
parent
6628220429
commit
2297350c6e
13 changed files with 174 additions and 118 deletions
21
CHANGELOG.md
21
CHANGELOG.md
|
@ -10,6 +10,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
## Unreleased
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- Migrated several functions in the `roblox` builtin to new, more flexible APIs:
|
||||
|
||||
- `readPlaceFile -> deserializePlace`
|
||||
- `readModelFile -> deserializeModel`
|
||||
- `writePlaceFile -> serializePlace`
|
||||
- `writeModelFile -> serializeModel`
|
||||
|
||||
These new APIs **_no longer use file paths_**, meaning to use them with files you must first read them using the `fs` builtin.
|
||||
|
||||
- Removed `CollectionService` and its methods from the `roblox` builtin library - new instance methods have been added as replacements.
|
||||
- Removed [`Instance:FindFirstDescendant`](https://create.roblox.com/docs/reference/engine/classes/Instance#FindFirstDescendant) which was a method that was never enabled in the official Roblox API and will soon be removed. <br/>
|
||||
Use the second argument of the already existing find methods instead to find descendants.
|
||||
|
||||
### Added
|
||||
|
||||
- Added `serde.compress` and `serde.decompress` for compressing and decompressing strings using one of several compression formats: `brotli`, `gzip`, `lz4`, or `zlib`.
|
||||
|
@ -38,12 +53,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- [`Instance:RemoveTag`](https://create.roblox.com/docs/reference/engine/classes/Instance#RemoveTag)
|
||||
- Implemented the second argument of the `FindFirstChild` / `FindFirstChildOfClass` / `FindFirstChildWhichIsA` instance methods.
|
||||
|
||||
### Removed
|
||||
|
||||
- Removed `CollectionService` and its methods from the `roblox` builtin library.
|
||||
- Removed [`Instance:FindFirstDescendant`](https://create.roblox.com/docs/reference/engine/classes/Instance#FindFirstDescendant) which was a method that was never enabled in the official Roblox API and will soon be removed. <br/>
|
||||
Use the second argument of the already existing find methods instead to find descendants.
|
||||
|
||||
### Changed
|
||||
|
||||
- Update to Luau version `0.576`
|
||||
|
|
|
@ -9,17 +9,22 @@ export type Instance = {}
|
|||
### Example usage
|
||||
|
||||
```lua
|
||||
local fs = require("@lune/fs")
|
||||
local roblox = require("@lune/roblox")
|
||||
|
||||
-- Reading & writing a place file
|
||||
local game = roblox.readPlaceFile("myPlaceFile.rbxl")
|
||||
local workspace = game:GetService("Workspace")
|
||||
-- Reading a place file
|
||||
local placeFile = fs.readFile("myPlaceFile.rbxl")
|
||||
local game = roblox.deserializePlace(placeFile)
|
||||
|
||||
-- Manipulating and reading instances - just like in Roblox!
|
||||
local workspace = game:GetService("Workspace")
|
||||
for _, child in workspace:GetChildren() do
|
||||
print("Found child " .. child.Name .. " of class " .. child.ClassName)
|
||||
end
|
||||
|
||||
roblox.writePlaceFile("myPlaceFile.rbxl", game)
|
||||
-- Writing a place file
|
||||
local newPlaceFile = roblox.serializePlace(game)
|
||||
fs.writeFile("myPlaceFile.rbxl", newPlaceFile)
|
||||
```
|
||||
]=]
|
||||
return {
|
||||
|
@ -27,70 +32,100 @@ return {
|
|||
@within Roblox
|
||||
@must_use
|
||||
|
||||
Reads a place file into a DataModel instance.
|
||||
Deserializes a place into a DataModel instance.
|
||||
|
||||
This function accepts a string of contents, *not* a file path.
|
||||
If reading a place file from a file path is desired, `fs.readFile`
|
||||
can be used and the resulting string may be passed to this function.
|
||||
|
||||
### Example usage
|
||||
|
||||
```lua
|
||||
local fs = require("@lune/fs")
|
||||
local roblox = require("@lune/roblox")
|
||||
local game = roblox.readPlaceFile("filePath.rbxl")
|
||||
|
||||
local placeFile = fs.readFile("filePath.rbxl")
|
||||
local game = roblox.deserializePlace(placeFile)
|
||||
```
|
||||
|
||||
@param filePath The file path to read from
|
||||
@param contents The contents of the place to read
|
||||
]=]
|
||||
readPlaceFile = function(filePath: string): Instance
|
||||
deserializePlace = function(contents: string): Instance
|
||||
return nil :: any
|
||||
end,
|
||||
--[=[
|
||||
@within Roblox
|
||||
@must_use
|
||||
|
||||
Reads a model file into a table of instances.
|
||||
Deserializes a model into an array of instances.
|
||||
|
||||
This function accepts a string of contents, *not* a file path.
|
||||
If reading a model file from a file path is desired, `fs.readFile`
|
||||
can be used and the resulting string may be passed to this function.
|
||||
|
||||
### Example usage
|
||||
|
||||
```lua
|
||||
local fs = require("@lune/fs")
|
||||
local roblox = require("@lune/roblox")
|
||||
local instances = roblox.readModelFile("filePath.rbxm")
|
||||
|
||||
local modelFile = fs.readFile("filePath.rbxm")
|
||||
local instances = roblox.deserializeModel(modelFile)
|
||||
```
|
||||
|
||||
@param filePath The file path to read from
|
||||
@param contents The contents of the model to read
|
||||
]=]
|
||||
readModelFile = function(filePath: string): { Instance }
|
||||
deserializeModel = function(contents: string): { Instance }
|
||||
return nil :: any
|
||||
end,
|
||||
--[=[
|
||||
@within Roblox
|
||||
@must_use
|
||||
|
||||
Writes a DataModel instance to a place file.
|
||||
Serializes a place from a DataModel instance.
|
||||
|
||||
This string can then be written to a file, or sent over the network.
|
||||
|
||||
### Example usage
|
||||
|
||||
```lua
|
||||
local fs = require("@lune/fs")
|
||||
local roblox = require("@lune/roblox")
|
||||
roblox.writePlaceFile("filePath.rbxl", game)
|
||||
|
||||
local placeFile = roblox.serializePlace(game)
|
||||
fs.writeFile("filePath.rbxl", placeFile)
|
||||
```
|
||||
|
||||
@param filePath The file path to write to
|
||||
@param dataModel The DataModel to write to the file
|
||||
@param dataModel The DataModel for the place to serialize
|
||||
@param xml If the place should be serialized as xml or not. Defaults to `false`, meaning the place gets serialized using the binary format and not xml.
|
||||
]=]
|
||||
writePlaceFile = function(filePath: string, dataModel: Instance) end,
|
||||
serializePlace = function(dataModel: Instance, xml: boolean?): string
|
||||
return nil :: any
|
||||
end,
|
||||
--[=[
|
||||
@within Roblox
|
||||
@must_use
|
||||
|
||||
Writes one or more instances to a model file.
|
||||
Serializes one or more instances as a model.
|
||||
|
||||
This string can then be written to a file, or sent over the network.
|
||||
|
||||
### Example usage
|
||||
|
||||
```lua
|
||||
local fs = require("@lune/fs")
|
||||
local roblox = require("@lune/roblox")
|
||||
roblox.writeModelFile("filePath.rbxm", { instance1, instance2, ... })
|
||||
|
||||
local modelFile = roblox.serializeModel({ instance1, instance2, ... })
|
||||
fs.writeFile("filePath.rbxm", modelFile)
|
||||
```
|
||||
|
||||
@param filePath The file path to write to
|
||||
@param instances The array of instances to write to the file
|
||||
@param instances The array of instances to serialize
|
||||
@param xml If the model should be serialized as xml or not. Defaults to `false`, meaning the model gets serialized using the binary format and not xml.
|
||||
]=]
|
||||
writeModelFile = function(filePath: string, instances: { Instance }) end,
|
||||
serializeModel = function(instances: { Instance }, xml: boolean?): string
|
||||
return nil :: any
|
||||
end,
|
||||
--[=[
|
||||
@within Roblox
|
||||
@must_use
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use blocking::unblock;
|
||||
use mlua::prelude::*;
|
||||
use tokio::fs;
|
||||
|
||||
use lune_roblox::{
|
||||
document::{Document, DocumentError, DocumentFormat, DocumentKind},
|
||||
|
@ -19,36 +16,19 @@ pub fn create(lua: &'static Lua) -> LuaResult<LuaTable> {
|
|||
}
|
||||
TableBuilder::new(lua)?
|
||||
.with_values(roblox_constants)?
|
||||
.with_async_function("readPlaceFile", read_place_file)?
|
||||
.with_async_function("readModelFile", read_model_file)?
|
||||
.with_async_function("writePlaceFile", write_place_file)?
|
||||
.with_async_function("writeModelFile", write_model_file)?
|
||||
.with_async_function("deserializePlace", deserialize_place)?
|
||||
.with_async_function("deserializeModel", deserialize_model)?
|
||||
.with_async_function("serializePlace", serialize_place)?
|
||||
.with_async_function("serializeModel", serialize_model)?
|
||||
.with_async_function("getAuthCookie", get_auth_cookie)?
|
||||
.build_readonly()
|
||||
}
|
||||
|
||||
fn parse_file_path(path: String) -> LuaResult<(PathBuf, DocumentFormat)> {
|
||||
let file_path = PathBuf::from(path);
|
||||
let file_ext = file_path
|
||||
.extension()
|
||||
.ok_or_else(|| {
|
||||
LuaError::RuntimeError(format!(
|
||||
"Missing file extension for file path: '{}'",
|
||||
file_path.display()
|
||||
))
|
||||
})?
|
||||
.to_string_lossy();
|
||||
let doc_format = DocumentFormat::from_extension(&file_ext).ok_or_else(|| {
|
||||
LuaError::RuntimeError(format!(
|
||||
"Invalid file extension for writing place file: '{}'",
|
||||
file_ext
|
||||
))
|
||||
})?;
|
||||
Ok((file_path, doc_format))
|
||||
}
|
||||
|
||||
async fn read_place_file(lua: &Lua, path: String) -> LuaResult<LuaValue> {
|
||||
let bytes = fs::read(path).await.map_err(LuaError::external)?;
|
||||
async fn deserialize_place<'lua>(
|
||||
lua: &'lua Lua,
|
||||
contents: LuaString<'lua>,
|
||||
) -> LuaResult<LuaValue<'lua>> {
|
||||
let bytes = contents.as_bytes().to_vec();
|
||||
let fut = unblock(move || {
|
||||
let doc = Document::from_bytes(bytes, DocumentKind::Place)?;
|
||||
let data_model = doc.into_data_model_instance()?;
|
||||
|
@ -57,8 +37,11 @@ async fn read_place_file(lua: &Lua, path: String) -> LuaResult<LuaValue> {
|
|||
fut.await?.to_lua(lua)
|
||||
}
|
||||
|
||||
async fn read_model_file(lua: &Lua, path: String) -> LuaResult<LuaValue> {
|
||||
let bytes = fs::read(path).await.map_err(LuaError::external)?;
|
||||
async fn deserialize_model<'lua>(
|
||||
lua: &'lua Lua,
|
||||
contents: LuaString<'lua>,
|
||||
) -> LuaResult<LuaValue<'lua>> {
|
||||
let bytes = contents.as_bytes().to_vec();
|
||||
let fut = unblock(move || {
|
||||
let doc = Document::from_bytes(bytes, DocumentKind::Model)?;
|
||||
let instance_array = doc.into_instance_array()?;
|
||||
|
@ -67,32 +50,36 @@ async fn read_model_file(lua: &Lua, path: String) -> LuaResult<LuaValue> {
|
|||
fut.await?.to_lua(lua)
|
||||
}
|
||||
|
||||
async fn write_place_file(_: &Lua, (path, data_model): (String, Instance)) -> LuaResult<()> {
|
||||
let (file_path, doc_format) = parse_file_path(path)?;
|
||||
async fn serialize_place(
|
||||
lua: &Lua,
|
||||
(data_model, as_xml): (Instance, Option<bool>),
|
||||
) -> LuaResult<LuaString> {
|
||||
let fut = unblock(move || {
|
||||
let doc = Document::from_data_model_instance(data_model)?;
|
||||
let bytes = doc.to_bytes_with_format(doc_format)?;
|
||||
let bytes = doc.to_bytes_with_format(match as_xml {
|
||||
Some(true) => DocumentFormat::Xml,
|
||||
_ => DocumentFormat::Binary,
|
||||
})?;
|
||||
Ok::<_, DocumentError>(bytes)
|
||||
});
|
||||
let bytes = fut.await?;
|
||||
fs::write(file_path, bytes)
|
||||
.await
|
||||
.map_err(LuaError::external)?;
|
||||
Ok(())
|
||||
lua.create_string(&bytes)
|
||||
}
|
||||
|
||||
async fn write_model_file(_: &Lua, (path, instances): (String, Vec<Instance>)) -> LuaResult<()> {
|
||||
let (file_path, doc_format) = parse_file_path(path)?;
|
||||
async fn serialize_model(
|
||||
lua: &Lua,
|
||||
(instances, as_xml): (Vec<Instance>, Option<bool>),
|
||||
) -> LuaResult<LuaString> {
|
||||
let fut = unblock(move || {
|
||||
let doc = Document::from_instance_array(instances)?;
|
||||
let bytes = doc.to_bytes_with_format(doc_format)?;
|
||||
let bytes = doc.to_bytes_with_format(match as_xml {
|
||||
Some(true) => DocumentFormat::Xml,
|
||||
_ => DocumentFormat::Binary,
|
||||
})?;
|
||||
Ok::<_, DocumentError>(bytes)
|
||||
});
|
||||
let bytes = fut.await?;
|
||||
fs::write(file_path, bytes)
|
||||
.await
|
||||
.map_err(LuaError::external)?;
|
||||
Ok(())
|
||||
lua.create_string(&bytes)
|
||||
}
|
||||
|
||||
async fn get_auth_cookie(_: &Lua, raw: Option<bool>) -> LuaResult<Option<String>> {
|
||||
|
|
|
@ -119,10 +119,10 @@ create_tests! {
|
|||
roblox_datatype_vector3: "roblox/datatypes/Vector3",
|
||||
roblox_datatype_vector3int16: "roblox/datatypes/Vector3int16",
|
||||
|
||||
roblox_files_read_model: "roblox/files/readModelFile",
|
||||
roblox_files_read_place: "roblox/files/readPlaceFile",
|
||||
roblox_files_write_model: "roblox/files/writeModelFile",
|
||||
roblox_files_write_place: "roblox/files/writePlaceFile",
|
||||
roblox_files_deserialize_model: "roblox/files/deserializeModel",
|
||||
roblox_files_deserialize_place: "roblox/files/deserializePlace",
|
||||
roblox_files_serialize_model: "roblox/files/serializeModel",
|
||||
roblox_files_serialize_place: "roblox/files/serializePlace",
|
||||
|
||||
roblox_instance_attributes: "roblox/instance/attributes",
|
||||
roblox_instance_new: "roblox/instance/new",
|
||||
|
|
|
@ -7,8 +7,11 @@ for _, dirName in fs.readDir("tests/roblox/rbx-test-files/places") do
|
|||
end
|
||||
|
||||
for _, modelDir in modelDirs do
|
||||
local modelBinary = roblox.readModelFile(modelDir .. "/binary.rbxl")
|
||||
local modelXml = roblox.readModelFile(modelDir .. "/xml.rbxlx")
|
||||
local modelFileBinary = fs.readFile(modelDir .. "/binary.rbxl")
|
||||
local modelFileXml = fs.readFile(modelDir .. "/xml.rbxlx")
|
||||
|
||||
local modelBinary = roblox.deserializeModel(modelFileBinary)
|
||||
local modelXml = roblox.deserializeModel(modelFileXml)
|
||||
|
||||
for _, modelInstance in modelBinary do
|
||||
assert(modelInstance:IsA("Instance"))
|
|
@ -7,8 +7,11 @@ for _, dirName in fs.readDir("tests/roblox/rbx-test-files/places") do
|
|||
end
|
||||
|
||||
for _, placeDir in placeDirs do
|
||||
local placeBinary = roblox.readPlaceFile(placeDir .. "/binary.rbxl")
|
||||
local placeXml = roblox.readPlaceFile(placeDir .. "/xml.rbxlx")
|
||||
local placeFileBinary = fs.readFile(placeDir .. "/binary.rbxl")
|
||||
local placeFileXml = fs.readFile(placeDir .. "/xml.rbxlx")
|
||||
|
||||
local placeBinary = roblox.deserializePlace(placeFileBinary)
|
||||
local placeXml = roblox.deserializePlace(placeFileXml)
|
||||
|
||||
assert(placeBinary.ClassName == "DataModel")
|
||||
assert(placeXml.ClassName == "DataModel")
|
|
@ -1,3 +1,4 @@
|
|||
local fs = require("@lune/fs")
|
||||
local roblox = require("@lune/roblox") :: any
|
||||
local Instance = roblox.Instance
|
||||
|
||||
|
@ -6,11 +7,17 @@ local instances = {
|
|||
Instance.new("Part"),
|
||||
}
|
||||
|
||||
roblox.writeModelFile("bin/temp-model.rbxm", instances)
|
||||
roblox.writeModelFile("bin/temp-model.rbxmx", instances)
|
||||
local modelAsBinary = roblox.serializeModel(instances)
|
||||
local modelAsXml = roblox.serializeModel(instances, true)
|
||||
|
||||
local savedBinary = roblox.readModelFile("bin/temp-model.rbxm")
|
||||
local savedXml = roblox.readModelFile("bin/temp-model.rbxmx")
|
||||
fs.writeFile("bin/temp-model.rbxm", modelAsBinary)
|
||||
fs.writeFile("bin/temp-model.rbxmx", modelAsXml)
|
||||
|
||||
local savedFileBinary = fs.readFile("bin/temp-model.rbxm")
|
||||
local savedFileXml = fs.readFile("bin/temp-model.rbxmx")
|
||||
|
||||
local savedBinary = roblox.deserializeModel(savedFileBinary)
|
||||
local savedXml = roblox.deserializeModel(savedFileXml)
|
||||
|
||||
assert(savedBinary[1].Name ~= "ROOT")
|
||||
assert(savedXml[1].Name ~= "ROOT")
|
31
tests/roblox/files/serializePlace.luau
Normal file
31
tests/roblox/files/serializePlace.luau
Normal file
|
@ -0,0 +1,31 @@
|
|||
local fs = require("@lune/fs")
|
||||
local roblox = require("@lune/roblox") :: any
|
||||
local Instance = roblox.Instance
|
||||
|
||||
local game = Instance.new("DataModel")
|
||||
|
||||
local workspace = game:GetService("Workspace")
|
||||
|
||||
local model = Instance.new("Model")
|
||||
local part = Instance.new("Part")
|
||||
|
||||
part.Parent = model
|
||||
model.Parent = workspace
|
||||
|
||||
local placeAsBinary = roblox.serializePlace(game)
|
||||
local placeAsXml = roblox.serializePlace(game, true)
|
||||
|
||||
fs.writeFile("bin/temp-place.rbxl", placeAsBinary)
|
||||
fs.writeFile("bin/temp-place.rbxlx", placeAsXml)
|
||||
|
||||
local savedFileBinary = fs.readFile("bin/temp-place.rbxl")
|
||||
local savedFileXml = fs.readFile("bin/temp-place.rbxlx")
|
||||
|
||||
local savedBinary = roblox.deserializePlace(savedFileBinary)
|
||||
local savedXml = roblox.deserializePlace(savedFileXml)
|
||||
|
||||
assert(savedBinary.Name ~= "ROOT")
|
||||
assert(savedXml.Name ~= "ROOT")
|
||||
|
||||
assert(savedBinary.ClassName == "DataModel")
|
||||
assert(savedXml.ClassName == "DataModel")
|
|
@ -1,24 +0,0 @@
|
|||
local roblox = require("@lune/roblox") :: any
|
||||
local Instance = roblox.Instance
|
||||
|
||||
local game = Instance.new("DataModel")
|
||||
|
||||
local workspace = game:GetService("Workspace")
|
||||
|
||||
local model = Instance.new("Model")
|
||||
local part = Instance.new("Part")
|
||||
|
||||
part.Parent = model
|
||||
model.Parent = workspace
|
||||
|
||||
roblox.writePlaceFile("bin/temp-place.rbxl", game)
|
||||
roblox.writePlaceFile("bin/temp-place.rbxlx", game)
|
||||
|
||||
local savedBinary = roblox.readPlaceFile("bin/temp-place.rbxl")
|
||||
local savedXml = roblox.readPlaceFile("bin/temp-place.rbxlx")
|
||||
|
||||
assert(savedBinary.Name ~= "ROOT")
|
||||
assert(savedXml.Name ~= "ROOT")
|
||||
|
||||
assert(savedBinary.ClassName == "DataModel")
|
||||
assert(savedXml.ClassName == "DataModel")
|
|
@ -1,4 +1,4 @@
|
|||
local fs = require("@lune/fs") :: any
|
||||
local fs = require("@lune/fs")
|
||||
local roblox = require("@lune/roblox") :: any
|
||||
|
||||
local BrickColor = roblox.BrickColor
|
||||
|
@ -15,7 +15,8 @@ local Vector2 = roblox.Vector2
|
|||
local Vector3 = roblox.Vector3
|
||||
local Instance = roblox.Instance
|
||||
|
||||
local model = roblox.readModelFile("tests/roblox/rbx-test-files/models/attributes/binary.rbxm")[1]
|
||||
local modelFile = fs.readFile("tests/roblox/rbx-test-files/models/attributes/binary.rbxm")
|
||||
local model = roblox.deserializeModel(modelFile)[1]
|
||||
|
||||
model:SetAttribute("Foo", "Bar")
|
||||
|
||||
|
@ -99,5 +100,6 @@ assert(folder:GetAttribute("Foo") == "Bar")
|
|||
local game = Instance.new("DataModel")
|
||||
model.Parent = game
|
||||
|
||||
local placeFile = roblox.serializePlace(game)
|
||||
fs.writeDir("bin/roblox")
|
||||
roblox.writePlaceFile("bin/roblox/attributes.rbxl", game)
|
||||
fs.writeFile("bin/roblox/attributes.rbxl", placeFile)
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
local fs = require("@lune/fs")
|
||||
local roblox = require("@lune/roblox") :: any
|
||||
local Instance = roblox.Instance
|
||||
|
||||
local model =
|
||||
roblox.readModelFile("tests/roblox/rbx-test-files/models/three-nested-folders/binary.rbxm")[1]
|
||||
local modelFile = fs.readFile("tests/roblox/rbx-test-files/models/three-nested-folders/binary.rbxm")
|
||||
local model = roblox.deserializeModel(modelFile)[1]
|
||||
|
||||
assert(#model:GetChildren() == 1)
|
||||
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
local fs = require("@lune/fs")
|
||||
local roblox = require("@lune/roblox") :: any
|
||||
local Instance = roblox.Instance
|
||||
|
||||
local model =
|
||||
roblox.readModelFile("tests/roblox/rbx-test-files/models/three-nested-folders/binary.rbxm")[1]
|
||||
local modelFile = fs.readFile("tests/roblox/rbx-test-files/models/three-nested-folders/binary.rbxm")
|
||||
local model = roblox.deserializeModel(modelFile)[1]
|
||||
|
||||
assert(#model:GetDescendants() == 2)
|
||||
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
local fs = require("@lune/fs")
|
||||
local roblox = require("@lune/roblox") :: any
|
||||
|
||||
local model =
|
||||
roblox.readModelFile("tests/roblox/rbx-test-files/models/three-nested-folders/binary.rbxm")[1]
|
||||
local modelFile = fs.readFile("tests/roblox/rbx-test-files/models/three-nested-folders/binary.rbxm")
|
||||
local model = roblox.deserializeModel(modelFile)[1]
|
||||
|
||||
local child = model:FindFirstChild("Parent")
|
||||
local descendant = child:FindFirstChild("Child")
|
||||
|
|
Loading…
Reference in a new issue