Created
February 14, 2015 16:27
-
-
Save sbseltzer/017f1a4e2c1ac718db6b to your computer and use it in GitHub Desktop.
A simple function that adds configurable swizzling to any metatable.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--[[ | |
MakeSwizzle | |
Add swizzling functionality to a metatable! | |
@param MOriginal - Original metatable to add swizzling to. | |
@param keys - Ordered array of key names we want to swizzle on. | |
@param components - Ordered array of characters to associate with the key names from the keys array. | |
@param defaults - Optional list of default values to be used when t[GetComponent(c)] == nil. | |
@param constructor - When swizzling on valid keys, a list of associated values preceded by the custom swizzle metatable is passed to the constructor (in keys) from the __index metamethod, that is, constructor(MSwizzle, ...). The result of this function is returned by the __index metamethod. | |
@return MOriginal - The original metatable (which has been modified). | |
@return GetKey(i) - A function that returns the key name at index i from the keys parameter. | |
@return GetComponent(c) - A function that returns the key name associated with component c as listed in the components parameter. | |
@return GetDefault(c) - A function that returns the default value for component c. | |
--]] | |
function MakeSwizzle(MOriginal, keys, components, defaults, constructor) | |
if (#keys ~= #components) then | |
error(2, "MakeSwizzle: length of keys must be same as components"); | |
return; | |
end | |
local translateComponent = {}; | |
local translateDefault = {}; | |
for i = 1, #components do | |
if (string.len(components[i]) > 1) then | |
error("MakeSwizzle: components cannot be more than one character in length", 2); | |
return; | |
end | |
translateComponent[components[i]] = keys[i]; | |
if (defaults) then | |
translateDefault[components[i]] = defaults[i]; | |
end | |
end | |
local GetKey = function(i) | |
return keys[i]; | |
end | |
local GetComponent = function(c) | |
return translateComponent[c]; | |
end | |
local GetDefault = function(c) | |
return translateDefault[c]; | |
end | |
local oldIndex = MOriginal.__index; | |
local oldNewIndex = MOriginal.__newindex; | |
-- Fancy index function for swizzling. | |
MOriginal.__index = function(t, key) | |
-- Only swizzle when key length is no greater than the number of valid swizzle keys. | |
if (string.len(key) <= #keys) then | |
-- Attempt to build a swizzled version of the table. | |
local swizzle = {}; | |
for i = 1, string.len(key) do | |
local c = string.sub(key,i,i); -- i-th character of the key | |
-- If there is not a matching component for this character, we are not swizzling. | |
if (not GetComponent(c)) then | |
swizzle = nil; | |
break; | |
end | |
-- Attempt to get the value of this key component. | |
local part = rawget(t, GetComponent(c)); | |
if (part == nil) then | |
if (not defaults) then | |
swizzle = nil; | |
break; | |
elseif (GetDefault(c) ~= nil) then | |
part = GetDefault(c); | |
end | |
end | |
-- Add it to the swizzled table. | |
swizzle[i] = part; | |
end | |
-- If building of the swizzled table was successful, pass it to the constructor. | |
if (swizzle) then | |
return constructor(unpack(swizzle)); | |
end | |
end | |
-- If we made it this far, we aren't swizzling. | |
local value = nil; | |
if (type(oldIndex) == "table") then | |
value = oldIndex[key]; | |
elseif (type(oldIndex) == "function") then | |
value = oldIndex(t, key); | |
end | |
if (value == nil) then | |
value = MOriginal[key]; | |
end | |
return value; | |
end | |
-- Fancy newindex function to prevent adding keys that could be misconstrued as swizzling. | |
MOriginal.__newindex = function(t, key, value) | |
if (string.len(key) <= #components) then | |
-- Uncomment following block to disable swizzling on newindex. | |
--[[local isSwizzle = true; | |
for i = 1, string.len(key) do | |
local c = string.sub(key,i,i); | |
isSwizzle = (GetComponent(c) ~= nil); | |
if (not isSwizzle) then | |
break; | |
end | |
end | |
if (isSwizzle) then | |
return; | |
end]] | |
local swizzle = {}; | |
for i = 1, string.len(key) do | |
local c = string.sub(key,i,i); -- i-th character of the key | |
-- If there is not a matching component for this character, we are not swizzling. | |
if (not GetComponent(c)) then | |
swizzle = nil; | |
break; | |
end | |
-- Attempt to get the value of this key component. | |
local part = rawget(t, GetComponent(c)); | |
if (part == nil) then | |
swizzle = nil; | |
break; | |
end | |
-- Add it to the swizzled table. | |
swizzle[i] = GetComponent(c); | |
end | |
-- If building of the swizzled table was successful, assign values accordingly | |
if (swizzle) then | |
-- If the value is a table and it's the same metatable | |
if (type(value) == "table" and getmetatable(value) == getmetatable(t)) then | |
-- Set relevant components to the corresponding values. | |
for i = 1, #swizzle do | |
-- set it to the ith component of the value | |
rawset(t, swizzle[i], value[GetKey(i)]); | |
end | |
else | |
-- Set relevant components to the same value. | |
for i = 1, #swizzle do | |
rawset(t, swizzle[i], value); | |
end | |
end | |
return;-- constructor(MSwizzle, GetKey, GetComponent, unpack(swizzle)); | |
end | |
end | |
if (oldNewIndex) then | |
oldNewIndex(t, key, value); | |
else | |
rawset(t, key, value); | |
end | |
end | |
return MOriginal, GetKey, GetComponent, GetDefault; | |
end | |
--[[ | |
-- Usage/Testing | |
local MVector = {}; | |
MVector.__index = MVector; | |
function Vector(...) | |
local args = {...}; | |
local t = {}; | |
if (#args == 0) then | |
t.n = 3; | |
elseif (#args == 1) then | |
t.n = args[1]; | |
table.remove(args, 1); | |
elseif (#args > 4) then | |
t.n = 4; | |
else | |
t.n = #args; | |
end | |
if (t.n <= 1) then | |
t.n = 3; | |
elseif (t.n > 4) then | |
t.n = 4; | |
end | |
for i = 1, t.n do | |
if (#args >= i) then | |
t[MVector.swizzle_GetKey(i)] = args[i]; | |
else | |
t[MVector.swizzle_GetKey(i)] = 0; | |
end | |
end | |
return setmetatable(t, MVector); | |
end | |
MVector.__add = function(a, b) | |
if (a.n ~= b.n) then | |
error("Attempted to add vectors of unequal dimension", 2); | |
end | |
local t = {}; | |
for i = 1, math.min(a.n, b.n) do | |
local c = MVector.swizzle_GetKey(i); | |
table.insert(t, a[c] + b[c]); | |
end | |
return Vector(unpack(t)); | |
end | |
MVector.__sub = function(a, b) | |
if (a.n ~= b.n) then | |
error("Attempted to sub vectors of unequal dimension", 2); | |
end | |
local t = {}; | |
for i = 1, math.min(a.n, b.n) do | |
local c = MVector.swizzle_GetKey(i); | |
table.insert(t, a[c] - b[c]); | |
end | |
return Vector(unpack(t)); | |
end | |
MVector.__tostring = function(t) | |
local s = ""; | |
for i = 1, t.n do | |
if (string.len(s) == 0) then | |
s = "Vector["; | |
else | |
s = s .. ", "; | |
end | |
if (type(t[MVector.swizzle_GetKey(i)]) ~= "number") then | |
s = s .. MVector.swizzle_GetKey(i); | |
else | |
s = s .. t[MVector.swizzle_GetKey(i)]; | |
end | |
end | |
return s .. "]"; | |
end | |
MVector, MVector.swizzle_GetKey, MVector.swizzle_GetComponent, MVector.swizzle_GetDefault = MakeSwizzle(MVector, {"x", "y", "z", "w"}, {'x', 'y', 'z', 'w'}, {0,0,0,0}, Vector); | |
local a = Vector(1, 2, 3, 4) | |
local b = Vector(3, 2, 1) | |
print (MVector, getmetatable(a)) | |
print (a.xyz + b, a - b.xyzw) | |
print (a, b) | |
print (a.xyz, b.xyz) | |
print (a.zyx, b.zyx) | |
print (a.xx, b.xx) | |
print (a.xyzw, b.xyzw) | |
print (a.xpos, b.xpos) | |
print (Vector(), Vector(-1), Vector(0), Vector(1), Vector(2), Vector(3), Vector(4), Vector(5)) | |
print (Vector(0,1), Vector(0,1,2), Vector(0,1,2,3), Vector(0,1,2,3,4)) | |
local MColor = {}; | |
MColor.__index = MColor; | |
function Color(...) | |
local args = {...}; | |
local t = {}; | |
if (#args == 0) then | |
t.n = 3; | |
elseif (#args == 1) then | |
t.n = args[1]; | |
table.remove(args, 1); | |
elseif (#args > 4) then | |
t.n = 4; | |
else | |
t.n = #args; | |
end | |
if (t.n <= 2) then | |
t.n = 3; | |
elseif (t.n > 4) then | |
t.n = 4; | |
end | |
for i = 1, t.n do | |
if (#args >= i) then | |
t[MColor.swizzle_GetKey(i)] = args[i]; | |
else | |
t[MColor.swizzle_GetKey(i)] = 0; | |
end | |
end | |
return setmetatable(t, MColor); | |
end | |
MColor.__tostring = function(t) | |
local s = ""; | |
for i = 1, t.n do | |
if (string.len(s) == 0) then | |
s = "Color["; | |
else | |
s = s .. ", "; | |
end | |
if (type(t[MColor.swizzle_GetKey(i)]) ~= "number") then | |
s = s .. MColor.swizzle_GetKey(i); | |
else | |
s = s .. t[MColor.swizzle_GetKey(i)]; | |
end | |
end | |
return s .. "]"; | |
end | |
MColor, MColor.swizzle_GetKey = MakeSwizzle(MColor, {'red', 'green', 'blue', 'alpha'}, {'r', 'g', 'b', 'a'}, nil, Color); | |
local c = Color(255, 255, 255, 255); | |
print(c); | |
c.rg = 0; | |
print(c); | |
c.b = 200; | |
print(c); | |
c.rgb = Color(1, 1, 1); | |
print(c); | |
c.ar = Color(2, 3); | |
print(c) | |
--]] | |
return MakeSwizzle; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment