-
Notifications
You must be signed in to change notification settings - Fork 1
Description
This is a vague proposal / food for thought / example of how I use AppleCake. It might not make sense to add this functionality to AppleCake itself, but I would like to at least share what I did - maybe it's going to be useful to somebody else.
Ever since I started using AppleCake more extensively, I realised it became a bit of a pain to wrangle the profile objects and to make sure the methods on different objects weren't re-using calls of the same method on different objects or recursive calls on the same method.
I realise the OO approach isn't a first-class thing in Lua and there's many ways to get it (I'm using middleclass right now), but it was an important enough use-case for me that I built up an AppleCake utility mixin to handle that.
Here's how I use it in real code:
ShaderStructArray = class('ShaderStructArray')
-- object fields to include in the profile args
ProfiledObject.includeIn(ShaderStructArray, {fields = {'name'}})
-- example method
function ShaderStructArray:markUnchanged()
self:trace()
for _, struct in ipairs(self._elements) do
struct:markUnchanged()
end
self:untrace()
endThis allows me to easily get more complicated traces like this one:

There's probably a better and more generic way of implementing it, but here's the code for the ProfiledObject mixin (you'll find some chunks of it stolen from the AppleCake implementation):
local function _methodNameAndPath(self)
local className = self.class.name or 'UnknownClass'
local name = 'unknownName'
local path = 'unknownPath'
local info = debug.getinfo(3, "fnS")
local line = ""
if info then
if info.name then
name = info.name
elseif info.func then -- Attempt to create a name from memory address
name = tostring(info.func):sub(10)
else
error("Could not generate name for this function")
end
if info.short_src then
line = (info.linedefined and "#"..info.linedefined or line)
path = info.short_src..line
end
end
-- return className .. '.' .. name .. '[' .. line .. ']', path
return string.format('%s.%s[%s]', className, name, line), path
end
local profilesFieldName = '_objectProfiles'
local function _profiles(self)
local profiles = rawget(self, profilesFieldName)
if not profiles then
profiles = {}
rawset(self, profilesFieldName, profiles)
end
return profiles
end
local function buildArgs(self, args)
args = args or {}
if self.class.profiledFieldNames then
for _, fieldName in ipairs(self.class.profiledFieldNames) do
args[fieldName] = rawget(self, fieldName)
end
end
return args
end
local function _profileName(baseName, recursionDepth)
if recursionDepth and recursionDepth > 0 then
-- baseName ..
return baseName .. '_level_' .. tostring(recursionDepth)
else
return baseName
end
end
local function startProfile(self, args, recursionDepth)
local name, path = _methodNameAndPath(self)
args = buildArgs(self, args)
-- print('method', _methodNameAndPath(self))
args.path = path
local profileName = _profileName(name, recursionDepth)
local profile = _profiles(self)[profileName]
profile = AppleCake.profile(name, args, profile)
_profiles(self)[profileName] = profile
end
local function mark(self, name, subname)
name = tostring(name)
if subname then
AppleCake.mark(name .. ' -> ' .. tostring(subname))
else
AppleCake.mark(name)
end
end
local function stopProfile(self, recursionDepth)
local profileName, _ = _methodNameAndPath(self)
profileName = _profileName(profileName, recursionDepth)
local profile = _profiles(self)[profileName]
profile:stop()
end
ProfiledObject = {
startProfile = startProfile,
stopProfile = stopProfile,
trace = startProfile,
untrace = stopProfile,
mark = mark,
}
ProfiledObject.includeIn = function(klass, opts)
klass.profiledFieldNames = opts.fields or {}
klass:include(ProfiledObject)
endAppleCake itself is impressively low-overhead, so I tried to keep the code light here and to have it not do too much unnecessary work.
Feel free to close the issue as I'm not sure it's really actionable, but again - I thought it was worth sharing