Skip to content

Instantly share code, notes, and snippets.

@sbseltzer
Created February 14, 2015 16:27
Show Gist options
  • Save sbseltzer/017f1a4e2c1ac718db6b to your computer and use it in GitHub Desktop.
Save sbseltzer/017f1a4e2c1ac718db6b to your computer and use it in GitHub Desktop.
A simple function that adds configurable swizzling to any metatable.
--[[
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