Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Lua.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<Project Path="sandbox/ConsoleApp1/ConsoleApp1.csproj"/>
<Project Path="sandbox/ConsoleApp2/ConsoleApp2.csproj"/>
<Project Path="sandbox/JitTest/JitTest.csproj"/>
<Project Path="sandbox/StringBufferTest/StringBufferTest.csproj" />
</Folder>
<Folder Name="/src/">
<Project Path="src/Lua.SourceGenerator/Lua.SourceGenerator.csproj"/>
Expand Down
317 changes: 317 additions & 0 deletions docs/StringBufferLibrary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,317 @@
# String Buffer Library

This is an implementation of the `string.buffer` module for **Lua-CSharp**.
Its design and API behavior are mainly inspired by
[**LuaJIT's `string.buffer`**](https://luajit.org/ext_buffer.html) module.
It is intended for efficient string concatenation and binary
serialization / deserialization of Lua values.

On the Lua side, this module provides an API similar to LuaJIT.
On the C# side, it is implemented as an extensible `StringBuffer` abstraction,
allowing custom serialization of **IUserData** and **LightUserData**.

## What It Can Do

* Build long strings efficiently
* Serialize / deserialize game data
* Fast table cloning

### Fast Table Cloning

You can easily implement a high-performance table cloning function,
provided that the table does not contain any non-serializable data.

```lua
local stringBuffer = require("string.buffer")
local encode = stringBuffer.encode
local decode = stringBuffer.decode

function fastCopy(tbl)
return decode(encode(tbl))
end
```

Below are the test results on my desktop system.
In `DEBUG` build mode, it is **4× faster** than a clone function implemented in pure Lua.
If the `options` parameter is provided and common string mappings are specified,
the performance can be even better.

```
=============== copy speed test ==================
deepCopy: 1.640625
fastCopy: 0.453125
```

See the test case for details:
[StringBufferTest](../sandbox/StringBufferTest/StringBufferTest.lua)

## How to Use

After creating a **LuaState**, call `LuaState.OpenStringBufferLibrary()`.

```cs
LuaState state = LuaState.Create();
state.OpenStringBufferLibrary();
state.OpenModuleLibrary();
```

Opening `StringBufferLibrary` does not expose the module to the global environment.
On the Lua side, you must explicitly require it using `require("string.buffer")`.

## API Overview

This library provides VS Code plugin support via
[Lua Language Server](https://luals.github.io/).
See [string.buffer.lua](../sandbox/StringBufferTest/string.buffer.lua) for details.

### **Type Definitions**

#### string.buffer.options

Encoding options for the string buffer.

##### Fields

* **maxRecursions**: number | nil
Maximum table recursion depth. When serializing a table, if the depth exceeds this value,
the behavior will be either throwing an exception or ignoring subtables,
depending on `suppressErrors`.
Default value: `32`. Internally represented as `System.Int32`.

* **suppressErrors**: boolean | nil
Suppress errors. When set to `true`, table serialization will not throw *recursion* exceptions.
Default value: `true`. Internally represented as `System.Boolean`.

* **dict**: string[] | nil
Key dictionary. An array of strings that are expected to appear as keys in the tables
being serialized. During serialization, these keys/values are compactly encoded
as indices. Choosing appropriate values can reduce size and improve performance.

* **metatable**: table[] | nil
Metatable list. An array of metatables used to record the metatables
of serialized table objects.

### **string.buffer**

#### string.buffer.new(size, options)

Create a `string.buffer.object`.

##### Parameters

* **size**: `number` | `nil`
Initial buffer size. Internally converted to `System.Int32`.
Default value: `32`.

* **options**: `string.buffer.options` | `nil`

##### Returns

* `string.buffer.object`

#### string.buffer.encode(value)

Serialize a Lua value into a string.

Supported types:

* `nil`
* `boolean`
* `number`
* `string`
* `table`

The following types are **not supported by default** and will throw an exception
unless extended on the C# side:

* `userdata`
* `lightuserdata`

The following types are **not supported at all**:

* `function`
* `thread`

##### Parameters

* **value**: `any`
The Lua value to serialize.

##### Returns

* `string`
The serialized binary string (byte array).

#### string.buffer.decode(str)

Deserialize a string into a Lua value.

##### Parameters

* **str**: `string`
A string produced by `string.buffer.encode`.

##### Returns

* `any`
The deserialized Lua value.

### string.buffer.object

#### string.buffer.object:encode(value)

Append the serialized byte data of a value to the internal buffer.

See ***string.buffer.encode(value)***.

##### Parameters

* **value**: `any`
The Lua value to serialize.

##### Returns

* `string.buffer.object`
The original buffer object, for method chaining.

#### string.buffer.object:decode(value)

See ***string.buffer.decode(value)***.

##### Parameters

* **value**: `string`
A string generated by `string.buffer.decode`.

##### Returns

* `any`
The deserialized Lua value.

#### string.buffer.object:reset()

Clear the buffer without releasing the allocated memory.
The memory can be reused.

##### Returns

* `string.buffer.object`
The original buffer object, for method chaining.

#### string.buffer.object:free()

Force memory release. All allocated memory will be cleared and left for garbage collection,
but the instance itself is not destroyed, so it can still be reused afterward.

#### string.buffer.object:put(...)

Append specific byte data to the buffer.

##### Parameters

* **...**: `string` | `number` | `table`
Data arguments. All data will be converted into byte data internally.
Note: tables must have a metatable containing a `__tostring` field,
otherwise an exception will be thrown.

##### Returns

* `string.buffer.object`
The original buffer object, for method chaining.

#### string.buffer.object:putf(fmt, ...)

Append formatted byte data to the buffer.
The format follows `string.format`.

##### Parameters

* **fmt**: `string`
Format string.

* **...**
Format arguments.

##### Returns

* `string.buffer.object`
The original buffer object, for method chaining.

#### string.buffer.object:set(str)

Copy string data into the buffer.
Unlike `string.buffer.object:put`, this does not append data.
Calling this method replaces all existing data in the buffer.

##### Parameters

* **str**: `string`
The string to write.

##### Returns

* `string.buffer.object`
The original buffer object, for method chaining.

#### string.buffer.object:skip(len)

Remove a specified number of bytes from the buffer.

##### Parameters

* **len**: `number`
The number of bytes to remove. Internally converted to `System.Int32`.

##### Returns

* `string.buffer.object`
The original buffer object, for method chaining.

#### string.buffer.object:get(...)

Remove a specified number of bytes from the buffer
and return all removed bytes as strings.

##### Parameters

* **...**: `integer`
The lengths of byte arrays to read.
If no parameters are provided, the operation applies to all
or the remaining byte data.

##### Returns

* ... `string`
One or more strings converted from the byte data.

#### string.buffer.object:tostring()

Convert the current byte data to a string and return it.
This operation does not remove any byte data.

The returned string is internally cached in the buffer object.
If no further byte operations are performed,
multiple calls to this function will not allocate new string memory.

##### Returns

* `string`
The cached string.

#### string.buffer.object:length()

Get the length of the byte data.

Due to certain characteristics of `Lua-CSharp`,
the length cannot be obtained via `#string.buffer.object`
(it always returns `nil`), so this function is provided instead.

##### Returns

* `number`
The length of the byte data.

## Other Notes

* Currently, the byte data length cannot be obtained via `#`,
which may be a bug in `Lua-CSharp`.
Loading