lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]

----- Original Message ----
From: RJP Computing <>
To: Lua list <>
Sent: Thursday, October 18, 2007 10:02:32 PM
Subject: Re: Multifunctions

On 10/18/07, Steven Johnson <> wrote:
If you're interested in seeing it, I've got a version that supports (single-inheritance) derived types and "anything goes" in the parameters.

It sounds interesting. I would like to see it. Thanks.
RJP Computing

Alright, here's a usage example. A guy with a spear versus a dragon,
and a simulation built up around that.
Untested and full of nonexistent functions:
-- Object-object hit test, two parameters. By default, objects don't collide.
local Hit = class.New("Multimethod", 2)
    return false
end, nil, nil)    -- nil: accept anything   
-- Post-hit response, ditto. No-op by default.
local Response = class.New("Multimethod", 2)
Response:Define(function() end)    -- omitting the nils
-- Hit test for a thrown spear and any kind of sphere (I dunno, floating rocks or something). If there's
-- an intersection, it also returns an information packet.
Hit:Define(function(sphere, spear)
    -- Sphere-ray intersection at time t: (c - (p + t * v)) . (c - (p + t * v)) = r^2
    local diff = sphere.c - spear.p
    local t = SolveQuadratic(spear.v * spear.v, -2 * diff * spear.v, diff * diff - sphere.r * sphere.r)
    if t then
        local point = spear.p + t * spear.v
        return true, { p = point, n = Normalize(point - sphere.c), t = t }
    return false
end, "Sphere", "Spear")    -- if not nil, name or unique id used to instantiate class, returned by Type (see below)
-- Response to the above. Note that since the multimethod only knows about two parameters, "how",
-- the collision info from the hit test, will be ignored when deciding the method dispatch.
Response:Define(function(sphere, spear, how)
    spear:BounceOff(how.p, how.n, sphere:SurfaceProperties())
end, "Sphere", "Spear")
-- Bubbles? In this case, "SquishySphere" is derived from "Sphere", so this gets called instead when
-- our spear meets the squishier sort of sphere.
Response:Define(function(Sphere, Spear, how)
    Spear:GetStuck(how.p, how.n)
end, "SquishySphere", "Spear")
-- If anything else ran into a sphere (normal, squishy, other), blow it up!
Response:Define(function(Sphere, OtherThing)
end, "Sphere", nil)
-- Hit test against a composite thing, in this case a dragon. If it hits a part, include
-- that part along with the usual collision info.
Hit:Define(function(dragon, spear)
    for part in dragon:GetParts() do
        local bHit, arg = Hit(part, spear)
        if bHit then
            return true, { part = part, info = arg }
end, "Dragon", "Spear")
-- Another composite case, in this case with one of the built-in types as a parameter.
Hit:Define(function(terrain_things, spear)
    for _, thing in ipairs(terrain_things) do
        local bHit, arg = Hit(thing, spear)
        if bHit then
            return true, arg
    return false
end, "table", "Spear")
-- The spear struck a dragon.
Response:Define(function(dragon, spear, how)
    local name = how.part:GetName()
    -- Spear to the head: dragon flinches and takes damage.
    if name == "head" then
    -- Cripple the wing if it's not already in tatters.
    elseif name == "wing" then
        if how.part:IsIntact() then
    -- Thick scales everywhere else.
       spear:BounceOff(,, how.part:SurfaceProperties())    
end, "Dragon", "Spear")
-- If the spear hits something else, it just drops dead.
Response:Define(function(object, spear)
    spear.v = Vec3(0, 0, 0)
end, nil, "Spear")
-- etc. etc. etc. All kinds of other interactions
-- Updates objects, and updates any that run into each other. Owing to laziness, objects are assumed
-- to be ordered so that the arguments are always passed in the order listed in the above signatures.
function RunDynamics (objects, dt)
    UpdateObjects(objects, dt)
    for i = 1, #objects do
        for j = i + 1, #objects do
            local bHit, how = Hit(objects[i], objects[j])
            if bHit then
                Response(objects[i], objects[j], how)
Comments on the implementation itself:
callops.IsCallable is just this:
    int IsCallable (lua_State * L)
        lua_pushboolean(L, lua_isfunction(L, 1) || luaL_getmetafield(L, index, "__call") != 0);
        return 1;
Right now, this only supports single inheritance, so class.Supers is actually only returning one value here... I'll probably generalize that
eventually with an iterator up the hierarchy linearization. The three class functions just have to behave like these:
    function IsType (item, what)
        assert(type ~= nil)
        local itype = item ~= nil and WeakTableOfClassInstances[item] or type(item)
            if itype == what then
                return true
            itype = Superclasses[itype]
        until itype == nil
        return false
    function Supers (type)
        assert(type ~= nil)
        return Superclasses[type]
    function Type (item)
        if item ~= nil then
            local itype = WeakTableOfClassInstances[item]
            -- Is an instance.
            if itype ~= nil then
                return itype, true
        -- Not an instance.
        return type(item), false

Do You Yahoo!?
Tired of spam? Yahoo! Mail has the best spam protection around

Attachment: Multimethod.lua
Description: Binary data