-
Notifications
You must be signed in to change notification settings - Fork 89
RFC: Extend buffer with a metatable #155
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 1 commit
5a67cf1
27a1865
375a0a1
8fe1ebd
81580dd
42a5765
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,114 @@ | ||||||
| # Extend buffer with a metatable | ||||||
|
|
||||||
| ## Summary | ||||||
|
|
||||||
| Extend `buffer.create` with a second argument which provides a metatable for that buffer. | ||||||
| The metatable and some of its keys will be frozen for potential improvements in access. | ||||||
|
|
||||||
| ## Motivation | ||||||
|
|
||||||
| Providing a buffer object with a metatable will allow creation of objects with small storage footprint which can also be matched to native structures on the side of the application using Luau. | ||||||
|
|
||||||
| By having a metatable, these buffer objects can have properties, methods and other kinds of operations. | ||||||
|
|
||||||
| This will also allow the behavior to be defined and extended on the Luau side, compared to userdata that is strictly defined by the host. | ||||||
|
|
||||||
| In a way, this will provide an alternative to `ctypes` in luajit. | ||||||
|
|
||||||
| ```luau | ||||||
| -- A float4 in a buffer | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not a float4.
Suggested change
|
||||||
| local mt = { | ||||||
| __index = function(b, field) | ||||||
| if field == "x" then return buffer.readf32(b, 0) | ||||||
| elseif field == "y" then return buffer.readf32(b, 4) | ||||||
| elseif field == "z" then return buffer.readf32(b, 8) | ||||||
| elseif field == "w" then return buffer.readf32(b, 12) | ||||||
| else error("unknown field") end | ||||||
| end, | ||||||
| __newindex = function(b, field, value) | ||||||
| if field == "x" then buffer.writef32(b, 0, value) | ||||||
| elseif field == "y" then buffer.writef32(b, 4, value) | ||||||
| elseif field == "z" then buffer.writef32(b, 8, value) | ||||||
| elseif field == "w" then buffer.writef32(b, 12, value) | ||||||
| else error("unknown field") end | ||||||
| end | ||||||
| } | ||||||
|
|
||||||
| local buf = buffer.create(16, mt) | ||||||
|
|
||||||
| buf.x = 2 | ||||||
| buf.y = 4 | ||||||
|
|
||||||
| assert(buf.x + buf.y + buf.z == 6) | ||||||
| ``` | ||||||
|
|
||||||
| Or alternatively: | ||||||
| ```luau | ||||||
| local mt = { | ||||||
| __index = { | ||||||
| x = function(b) return buffer.readf32(b, 0) end, | ||||||
| y = function(b) return buffer.readf32(b, 4) end, | ||||||
| z = function(b) return buffer.readf32(b, 8) end, | ||||||
| w = function(b) return buffer.readf32(b, 12) end, | ||||||
| setx = function(b, value) buffer.writef32(b, 0, value) end, | ||||||
| sety = function(b, value) buffer.writef32(b, 4, value) end, | ||||||
| setz = function(b, value) buffer.writef32(b, 8, value) end, | ||||||
| setw = function(b, value) buffer.writef32(b, 12, value) end, | ||||||
| magnitude = function(b) return math.sqrt(b.x * b.x + b.y * b.y + b.z * b.z + b.w * b.w) end, | ||||||
| normalize = function(b) return ... end, | ||||||
| }, | ||||||
| } | ||||||
|
|
||||||
| local buf = buffer.create(16, mt) | ||||||
|
|
||||||
| buf:setx(2) | ||||||
| buf:sety(4) | ||||||
| buf:normalize() | ||||||
|
|
||||||
| local xn = buf:x() | ||||||
| ``` | ||||||
|
|
||||||
| Or any other custom way the developer wants property access to be performed. | ||||||
|
|
||||||
| ## Design | ||||||
|
|
||||||
| `buffer.create(size: number, metatable: table): buffer` | ||||||
|
vegorov-rbx marked this conversation as resolved.
Outdated
|
||||||
|
|
||||||
| Buffer construction will accept a second argument which will be a metatable for the buffer object. | ||||||
|
|
||||||
| This metatable will be frozen. | ||||||
| In addition to that, `__index` and `__newindex` fields will also be frozen if they are a table. | ||||||
|
vegorov-rbx marked this conversation as resolved.
|
||||||
| Table freezing will have the same limitation as `table.freeze`, throwing an error if table metatable is locked. | ||||||
| Any of these tables can be frozen before the call. | ||||||
|
|
||||||
| When `__index` or `__newindex` is a function, VM will be allowed to ignore changes to the environment of those function. | ||||||
|
|
||||||
| The freezing is performed to provide additional guarantees for field access and other metamethod evaluation to unlock potential optimization opportunities. | ||||||
| This is similar to limitations placed on ctypes by luajit. | ||||||
| Having said that, this RFC doesn't make a promise of a particular implementation making those optimizations, so should be viewed as a buffer object usability improvement first. | ||||||
|
|
||||||
| VM metatable lookups and `getmetatable` will look for the buffer object metatable first and then fall back to the global buffer metatable. | ||||||
| This preserves the existing buffer extensibility point for hosts. | ||||||
|
|
||||||
| `setmetatable` will not be supported on buffer objects, the metatable reference is immutable. | ||||||
|
|
||||||
| Equality checks in the VM will call `__eq` for buffers similar to tables and userdata. | ||||||
|
vegorov-rbx marked this conversation as resolved.
|
||||||
|
|
||||||
| `__type` metatable key is ignored by `typeof`. As before, only host is allowed to define type names. | ||||||
|
|
||||||
| In order for the typechecker to understand buffers with attached metatables, we propose extending the intersections to be allowed on buffers, similar to `extern` types: | ||||||
|
|
||||||
| `type Obj = buffer & { x: number, y: number, z: number }` | ||||||
|
vegorov-rbx marked this conversation as resolved.
Outdated
|
||||||
|
|
||||||
| ## Drawbacks | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This doesn't seem to mention anything about the performance characteristics of
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would like to also add another drawback not mentioned here: buffers doesn't let you store any pointer indirections for any other Luau objects, so you're unable to store nested buffers, strings, instances, tables, etc, limiting their usefulness as data structures.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And on the subject of the lack of pointer indirections: if you have a buffer of length 5, and you have a |
||||||
|
|
||||||
| This increases buffer size by 8 bytes, with the bigger impact on 0-8 byte buffers going from 16 to 24 bytes and allocated from 32 byte page. | ||||||
|
|
||||||
| This RFC also introduces a special evaluation rule for metamethod functions. | ||||||
| While it is introduced for potential improvement in caching of operations, this might come at a surprise to users of deprecated environment modification functions. | ||||||
|
|
||||||
| ## Alternatives | ||||||
|
|
||||||
| Instead of extending the buffer object with a limited set of functionality, we might pursue a new kind of object like Records were which can build internal field mappings for an easier optimization path. | ||||||
|
|
||||||
| Another possibility is some alternative way of specifying the fields that would support building both the right __index function and internal acceleration structures. | ||||||
Uh oh!
There was an error while loading. Please reload this page.