diff --git a/CHANGELOG.md b/CHANGELOG.md
index 39fe2b7..40008e2 100644
--- a/CHANGELOG.md
+++ b/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.
+ 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.
- Use the second argument of the already existing find methods instead to find descendants.
-
### Changed
- Update to Luau version `0.576`
diff --git a/docs/typedefs/Roblox.luau b/docs/typedefs/Roblox.luau
index aa392b4..7e0bc9c 100644
--- a/docs/typedefs/Roblox.luau
+++ b/docs/typedefs/Roblox.luau
@@ -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
diff --git a/packages/lib/src/builtins/roblox.rs b/packages/lib/src/builtins/roblox.rs
index 7c930bf..c4184fd 100644
--- a/packages/lib/src/builtins/roblox.rs
+++ b/packages/lib/src/builtins/roblox.rs
@@ -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 {
}
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 {
- let bytes = fs::read(path).await.map_err(LuaError::external)?;
+async fn deserialize_place<'lua>(
+ lua: &'lua Lua,
+ contents: LuaString<'lua>,
+) -> LuaResult> {
+ 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 {
fut.await?.to_lua(lua)
}
-async fn read_model_file(lua: &Lua, path: String) -> LuaResult {
- let bytes = fs::read(path).await.map_err(LuaError::external)?;
+async fn deserialize_model<'lua>(
+ lua: &'lua Lua,
+ contents: LuaString<'lua>,
+) -> LuaResult> {
+ 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 {
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),
+) -> LuaResult {
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)) -> LuaResult<()> {
- let (file_path, doc_format) = parse_file_path(path)?;
+async fn serialize_model(
+ lua: &Lua,
+ (instances, as_xml): (Vec, Option),
+) -> LuaResult {
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) -> LuaResult