Need some help New Environments still setting _G

I’m trying to refine the run code for Codea Community but I’m having an issue with physics being left over when a new project is ran. I’m loading code with loadstring and running it under a new environment for Globals. However, if you run the Balls first then the Flick project, you will see that Flick is already getting Physics properties pre set. I’m not sure what I am over looking. Any help would be appreciated!


--# Main
function setup()
    --Monitor _G
    --No idea why collide is called so i had to remove it do to spam
    setmetatable(_G,{__index = function(t,k) if k ~= "collide" then print("_G Can't find ",k)end end ,
        __newindex = function(t,k,v) rawset(t,k,v) print("Adding to _G",t,k,v) end})
            
    --Save Original setup,draw and touch
    osetup = setup
    odraw = draw
    otouched = touched
    txt = "Original Draw"
    
    parameter.action("Original",function() callBack(1) end)
    parameter.action("Balls",function() callBack(2) end)
    parameter.action("Flick",function() callBack(3) end)
    
    --Get the projects
    local Flick = readProjectTab("Flick")
    Flick = string.gsub(Flick,"--%[%[","")
    Flick = string.gsub(Flick,"%]%]","")
    local Balls = readProjectTab("Balls")
    Balls = string.gsub(Balls,"--%[%[","")
    Balls = string.gsub(Balls,"%]%]","")
    projects = {"empty",Balls,Flick}

end

function draw()
    background(255, 255, 255, 255)
   text(txt,WIDTH/2,HEIGHT/2) 
end

function touched(touch)
   if touch.state == ENDED then print("touched") end 
end

function callBack(proj)
    if proj == 1 then
        _G.setup = osetup
        _G.draw = odraw
        _G.touched = otouched
        osetup()
    else
    print("New Project Loaded.")
    
    --setup new environment
    new_Env = nil
        new_Env = {}
    
    setmetatable(new_Env,{__index = _G,
        __newindex = function(t,k,v) rawset(t,k,v) print("new_Env set ",k,v) end})
    local str = projects[proj]
        
    --Point the new setup,draw,touch to _G    
    str = str..[[

    _G.setup = function() pushStyle() pushMatrix() setup() popMatrix() popStyle() end
    _G.setup()
    _G.draw = draw
    _G.touched = touched
]]
    
    --Load the string in its own environment
    local f = loadstring(str)
    setfenv(f,new_Env)
    f()
    end
end




--# Balls
--[[
-- class

-- Use this function to perform your initial setup
function setup()
    b={}
    for i=1,5 do
        b[i]=Ball()
    end
    r={}
    for i=1,5 do
        r[i]=Rect()
    end    
    CreateWalls()
end

-- This function gets called once every frame
function draw()
    background(195, 195, 201, 255)
    for i=1,#b do
        b[i]:draw()
    end
    for i=1,#r do
        r[i]:draw()
   end
end

function CreateWalls()
    leftWall = CreateWall(0,0,0,HEIGHT,0.0) 
    rightWall = CreateWall(WIDTH,0,WIDTH,HEIGHT,0.9) 
    bottomWall = CreateWall(0,0,WIDTH,0,0.2) 
    topWall = CreateWall(0,HEIGHT,WIDTH,HEIGHT,0.7)
end

--this function creates one wall (actually just a line)
function CreateWall(x,y,x1,y1,r)
    local w = physics.body(EDGE,vec2(x,y),vec2(x1,y1)) -- vec2
    w.restitution=r --see comment above
    return w
end

Ball = class()

function Ball:init(x,y,d,c)
    self.x=x or math.random(0,WIDTH)
    self.y=y or math.random(0,HEIGHT)
    self.diameter=d or math.random(50,200)
    self.colr=c or color(math.random(0,255),math.random(0,255),math.random(0,255))
    self.p=Physics(CIRCLE,{self.diameter/2},self.x,self.y) --physics uses radius
end

function Ball:draw()
    pushStyle() -- store style settings
    fill(self.colr) --set color
    pushMatrix()
    local x,y,a=self.p:currentPosition()
    translate(x,y)
    rotate(a)
    ellipse(0,0,self.diameter)
    popMatrix()
    popStyle() -- put back style settings
end

Rect = class()

function Rect:init(x,y,w,h,c)
    self.x=x or math.random(0,WIDTH)
    self.y=y or math.random(0,HEIGHT)
    self.width=w or math.random(60,150)
    self.height=h or math.random(30,100)
    self.colr=c or color(math.random(0,255),math.random(0,255),math.random(0,255))
    local data={}
    table.insert(data,vec2(-self.width/2,-self.height/2))
    table.insert(data,vec2(-self.width/2,self.height/2))
    table.insert(data,vec2(self.width/2,self.height/2))
    table.insert(data,vec2(self.width/2,-self.height/2))
    self.p=Physics(POLYGON,data,self.x,self.y)
end

function Rect:draw()
    pushStyle() -- store style settings
    fill(self.colr) --set color
    pushMatrix()
    local x,y,a=self.p:currentPosition()
    translate(x,y)
    rotate(a)
    rect(-self.width/2,-self.height/2,self.width,self.height)
    popMatrix()
    popStyle() -- put back style settings
end

Physics = class()

--type=CIRCLE or POLYGON
--data is a table. For circles it is one value, the radius, while for a polygon it is a set of vec2
--x,y is the initial position of the centre
--a is the initial angle
--g is the gravity value, 0=tabletop, 1=normal (things fall down)
--r is the restitution, ie springiness
--f is friction
--lv is linear velocity
--i is any info you want to store to identify this object, eg a name
function Physics:init(type,data,x,y,a,g,r,f,lv,i)  
    if type==CIRCLE then
        self.body=physics.body(CIRCLE,data[1]) 
    elseif type==POLYGON then
        self.body=physics.body(POLYGON,unpack(data))
    end
    self.body.x=x
    self.body.y=y
    self.body.angle=a or 0
    if g then self.body.gravityScale=1 else self.body.gravityScale=0 end
    self.body.restitution=r or 1
    self.body.friction=f or 0.1
    if lv==nil then lv=vec2(100+math.random(400),100+math.random(400)) end
    self.body.linearVelocity=lv
    self.body.info=i
end

function Physics:currentPosition()
    return self.body.x, self.body.y,self.body.angle
end

]]
--# Flick
--[[

-- Use this function to perform your initial setup
function setup()
    --displayMode(FULLSCREEN)
    PhysCreate:setup()
    -- ball 
    ball = physics.body(CIRCLE,40)
    ball.x = WIDTH/4
    ball.y = HEIGHT/2
    ball.interpolate = true
    ball.restitution = 0.4
    -- walls
    vert={vec2(WIDTH,HEIGHT),vec2(0,HEIGHT),vec2(0,0),vec2(WIDTH,0),vec2(WIDTH,HEIGHT)}
    floor = physics.body(CHAIN,unpack(vert))
    
    --Fixed to prevent holding spam to _G
   holding = 0

    -- store catapult variables
    pos = vec2(150,400)
    tpos = vec2()
    force = 0

    -- create boxes in grid
    bodies = {}
    local y = 1
    local x = 1
    for i = 1,45 do
        bodies[i] = PhysCreate:create(vec2(600+x*41,y*40),vec2(2,2))
        -- x < n, n sets the grid width
        if x < 5 then
            x=x+1
        else 
            y=y+1
            x=1
        end
    end
end

function touchPos()
    --print(vec2(CurrentTouch.x,CurrentTouch.y))
    return vec2(CurrentTouch.x,CurrentTouch.y)
end

-- This function gets called once every frame
function draw()
    -- This sets a dark background color 
    background(40, 40, 50)

    -- start touch and store first position
    if CurrentTouch ~= nil and CurrentTouch.state == BEGAN and touchPos().x < 300 then
        holding = 1
        pos = touchPos() 
    end

    -- keep ball in the air while in red zone
    if ball.x < 300 then
        if ball.y < 100 then
            ball.linearVelocity = vec2(ball.linearVelocity.x*0.3,(100-ball.y)*5)
        end
    end

    -- grab ball and set force
    if holding == 1 then
        local bl = (touchPos()-vec2(ball.x,ball.y)):normalize()
        ball.linearVelocity = bl*touchPos():dist(vec2(ball.x,ball.y))*5

        force = touchPos():dist(pos)
    end

    -- release catapult and set ball velocity    
    if holding == 1 and CurrentTouch.state == ENDED and touchPos():dist(vec2(ball.x,ball.y)) < 40 then
        ball.linearVelocity = (pos-touchPos())*force
        holding = 0
    end


    -- Set catapult starting variable if in red zone
    if ball.x > 300 and CurrentTouch.state == BEGAN and touchPos().x < 300 and holding == 0 then
        holding = 1
    elseif ball.x > 300 and touchPos().x > 300 and CurrentTouch.state == MOVING then
        holding = 0
    end

    -- draw boxes
    PhysCreate:draw()

    -- draw gui
    -- catapult area (red zone)
    line(300,0,300,HEIGHT)
    strokeWidth(0)
    fill(250,50,50,50)
    rect(0,0,300,HEIGHT)
    -- catapult
    if holding == 1 then
        strokeWidth(3)
        fill(186, 186, 186, 255)
        ellipse(pos.x,pos.y,20)

        fill(255)
        line(pos.x,pos.y,touchPos().x,touchPos().y)
    end
    -- draw ball
    strokeWidth(5)
    fill(100)
    ellipse(ball.x,ball.y,80)
end

---------
PhysCreate = class()

function PhysCreate:init(x)
    self.x = x
end

function PhysCreate:setup()
    Di = 0
    DTbl = {}
end

function PhysCreate:create(vpos,vsize)
    local vert = {vec2(-10*vsize.x,10*vsize.y),vec2(-10*vsize.x,-10*vsize.y),
    vec2(10*vsize.x,-10*vsize.y),vec2(10*vsize.x,10*vsize.y)}

    local p = physics.body(POLYGON,unpack(vert))
    p.mass = 2
    p.x = vpos.x
    p.y = vpos.y
    p.w = vsize.x*10*2
    p.h = vsize.y*10*2
    p.interpolate = true
    p.sleepingAllowed = false

    p.m = mesh()
    local mr = p.m:addRect(p.x,p.y,p.w,p.h,0)
    p.m:setRectTex(mr,0,0,1,1)

    Di = Di + 1
    DTbl[Di] = {p.m,mr,p}
end

function PhysCreate:draw()
    for i=1,#DTbl do
        local d = DTbl[i]
        d[1]:draw()
        d[1]:setRect(d[2],d[3].x,d[3].y,d[3].w,d[3].h,d[3].angle/57.3)
    end
end

function PhysCreate:touched(touch)
    -- Codea does not automatically call this method
end

]]

I will take a look , wait

Proyect tab Flick and Balls not found said in laine 37 and 41

It’s not pasting well, its adding an extra line at the bottom of each tab. Balls and flick should have ]] on the last line. My shortcut with string.sub isnt working lol.

Fixed above code.

@dave1707 I’m been looking but so far no luck.

Well a physics.body is stored as userdata. I can read the metatable from that userdata but not sure what to do from there.

@Briarfox There’s a lot of things stored as userdata. Would rigidbody help. See example code.


function setup()
    circ1=physics.body(CIRCLE,10)
    circ2=physics.body(CIRCLE,10)
    
    tab={}
    table.insert(tab,physics.body(CIRCLE,10))
    table.insert(tab,physics.body(CIRCLE,10))
    
    for x,y in pairs(_G) do
        if string.find(tostring(y),"rigidbody") then
            print(x,y)
        elseif type(y)=="table" and x~="_G" then
            for c,d in pairs(y) do
                if string.find(tostring(d),"rigidbody") then
                    print(x,c,d)
                end
            end
        end
    end
end

Got it! Thanks @dave1707. I use the metatables to store all rigidbodies into their own table so I can keep track of them. When they are looked up I use the metatable in _G to find them!

function setup()
    rigidTbl = {}
    setmetatable(_G,{__index = function(t,k) print("Accessing ",k) return rawget(rigidTbl,k) end,
        __newindex = function(t,k,v)
            
        if string.find(tostring(v),"rigidbody") then rigidTbl[k] = v print("body found") 
        else
        rawset(t,k,v)
        end
        print("New index _G ","Key: ",k," Value: ",v)
        end})
   b = physics.body(CIRCLE,25)
print(b)


end

err xml error double post

@Briarfox - physics bodies are garbage collected when there are no more references to them, but it can take a minute or more before the next collection. Have you tried manually forcing garbage collection immediately after deleting all references, using the command collectgarbage() ?

my two bucks. bodys are haunted…
It seem that physics live outside the main Codea loop. I suspects that there is some object pooling / reuse. Bodys aren’t cleared until you explicitly call their destroy method.

Yeah thats what I was afraid of :frowning:

It’s about time for halloween. It’s suitable. Sorry, I don’t know.

No! You guys are supose to have the answers when I fall short! :slight_smile:

@Briarfox physics objects stay until they are explicitly destroyed. I found that out when I tried doing restarts on a program the used physics.body. In setup(), I had to check for the bodies and destroy them if they still existed. Is there anyway you can identify what objects are still around and destroy them before loading another project. Maybe something in _G. I haven’t tried looking there.

Scary…