ID:2079192
 
(See the best response by Ter13.)
Code:
area
Space


Enter(mob/m) // this is to add the background to the player
. = ..()
if(ismob(m))
if(m.client)
var image/i = image('nebula2wGalaxy.png', m, "", DC_TURF_LAYER - 0.1)
i.text = "spacebg"
m << i

Exit(mob/m) // when they leave remove the image
. = ..()
if(ismob(m))
if(m.client)
var client/c = m.client
for(var/image/i in c.images)
if(i.text == "spacebg")
c.images -= i
del i


Problem description:

So, I'm trying to create a 3D ish view of space, and I want this background image to move with the player's mob as they traverse space. The image is 1000x667 and is 923kb in size.

The problem I'm facing is, outputting the image has some wildly weird behavior. The game will not boot up. Normally, it boots up to about 96mb in the task manager, pauses for a moment, then rockets up to about 900mb, and stabilizes down to 225mb. With this, it slowly climbs after 96mb, and after like 5 minutes or so its barely reached 180mb. In short, the game is being lagged to hell by something, and that just baffles me. As far as I'm aware, there is no way for the code to get called to slow it down like that.

There are no mobs in space upon booting up, so this shouldn't get called even a single time. After commenting out the image output, the game works fine (but obviously without the goal achieved.)

Why is this happening?

Best response
You should be using Entered and Exited(), not Enter() and Exit().

When the player logs into the world, the engine tries to move to the first turf where Enter() returns true. It will try every tile until it finds one.

What's going on is that Enter() is being called a huge number of times trying to enter turfs, and also attempting to enter the area the turf is in.

This is creating thousands of image objects all at one time. Since Exit() is never called by Login(), you have a leak going on. These objects will never be destroyed. Also, every time the player fails to move into space, it will create a new image for the player regardless of whether they are actually standing in space. That's another leak.

Also, your entire method for handling these image objects is really, really messed up in the first place and eating way more CPU than it needs to.

Let me fix:

client
var
image/spaceimg
proc
ShowSpace()
if(!spaceimg) spaceimg = image('nebula2wGalaxy.png',null,"",DC_TURF_LAYER - 0.1)
if(!spaceimg.loc)
spaceimg.loc = mob
images += spaceimg
HideSpace()
if(spaceimg.loc)
images -= spaceimg
spaceimg.loc = null

area
space
Entered(mob/m)
if(istype(m)&&m.client)
m.client.ShowSpace()
..()
Exited(mob/m)
if(istype(m)&&m.client)
m.client.HideSpace()
..()


I say this a lot:

for(var/image/i in c.images)
if(i.text == "spacebg")
c.images -= i
del i


If you ever find yourself searching through a list for an object that you know is there, you are using the slowest method possible of grabbing that object, and the more you use this kind of a pattern, the slower your game will be.

You should NEVER be in a situation where you are searching a list when you only care about one object.


I also say this a lot:

        Enter(mob/m) // this is to add the background to the player
. = ..()
if(ismob(m))
if(m.client)
var image/i = image('nebula2wGalaxy.png', m, "", DC_TURF_LAYER - 0.1)
i.text = "spacebg"
m << i

Exit(mob/m) // when they leave remove the image
. = ..()
if(ismob(m))
if(m.client)
var client/c = m.client
for(var/image/i in c.images)
if(i.text == "spacebg")
c.images -= i
del i


You shouldn't ever find yourself creating and deleting objects over and over again unless there is a need for a different object every time. Create it once on demand and then hang on to it. There's no real negative to keeping objects around if you are going to use them again in the future. There's a huge negative to creating them over and over and over again --as you just found out.

Also: Enter() is not called when an object moves into a square. Enter() is called to ask if it can occupy a square. Exit() is called to ask if it can leave a square. Enter() and Exit() don't even imply that an object will try to enter or leave a tile. It's just asking if it can. Entered() and Exited() will always be called only after a movable has successfully exited a tile via the Move() function. Odds are if you are doing something in Enter() and Exit() that doesn't contribute to the answer to the question: "Can I come in/go out?", you shouldn't be doing it there.
Thanks for the quick response Ter, I've taken your words into consideration, and made the changes. Here is what I have now:

client
var
tmp/image/spacebg

proc
addSpaceBG()
if(!spacebg) spacebg = image('nebula2wGalaxy.png', mob, "", DC_TURF_LAYER - 0.1) // if there isnt an image make one
if(!spacebg.loc) // if it has no location, give it one
spacebg.loc = mob
images += spacebg

hideSpaceBG()
if(spacebg.loc) // if it has a location, hide it from view
images -= spacebg
spacebg.loc = null

area
Space


Entered(mob/m) // this is to add the background to the player
if(m && m.client)
m.client.addSpaceBG()
. = ..()

Exited(mob/m) // when they leave remove the image
if(m && m.client)
m.client.hideSpaceBG()
. = ..()


Before removing the loc in hideSpaceBG() the game would boot up. After adding that line, the spacebg.loc = null, now the game is back to loading up really slowly. I hope you can spot the issue, because I sure can't.


Edit: Nvm, it was my outputs. Now I just need to test and see if this is working.
Seems the image isn't showing up. I verified that the file name is typed correctly, not sure what the issue is.
Glad you found it, because I was at a loss.

Also, a 923kb image is going to eat a little bit of time where the client will be completely frozen the first time it is rendered. There's no current way to improve this with the way the engine works.
if(!spacebg) spacebg = image('nebula2wGalaxy.png', mob, "", DC_TURF_LAYER - 0.1) // if there isnt an image make one

That line's the problem. change mob to null, I wasn't thinking when I wrote that.
In response to Ter13
Yeah I thought that was the culprit too. I tried it, and it didn't seem to solve the problem. I added some outputs to the procs to test if they were running.

client
var
tmp/image/spacebg

proc
addSpaceBG()
if(!spacebg) spacebg = image('nebula2wGalaxy.png', null, "", DC_TURF_LAYER - 0.1) // if there isnt an image make one
if(!spacebg.loc) // if it has no location, give it one
spacebg.loc = mob
images += spacebg
src << "Added"

hideSpaceBG()
if(spacebg.loc) // if it has a location, hide it from view
images -= spacebg
spacebg.loc = null
src << "Removed"


They are indeed running. I'm not really sure what the problem is if the image is getting added, lol.
if(m && m.client)

I typoed again. This isn't going to fix it, but it should be if(istype(m)&&m.client)

Sorry again. Typing quick. I've been pounding out some really tedious code all day.
Hey, no problem man, the help is appreciated. At least now the game boots up. I noticed that I'm putting it below the turfs, which I planned to do on purpose because I'm going to try to make the space turfs transparent. However.. I didnt make them transparent yet. This is working fine xD.
Thanks for your help!

Here is the final product I believe, still gotta test it.

client
var
tmp/image/spacebg

proc
addSpaceBG()
if(!spacebg)
spacebg = image('nebula2wGalaxy.png', null, "", DC_TURF_LAYER + 0.1) // if there isnt an image make one
spacebg.pixel_x -= bound_width / 2
spacebg.pixel_y -= bound_height / 2
if(!spacebg.loc) // if it has no location, give it one
spacebg.loc = mob
images += spacebg

hideSpaceBG()
if(spacebg.loc) // if it has a location, hide it from view
images -= spacebg
spacebg.loc = null

area
Space


Entered(mob/m) // this is to add the background to the player
if(istype(m) && m.client)
m.client.addSpaceBG()
. = ..()

Exited(mob/m) // when they leave remove the image
if(istype(m) && m.client)
m.client.hideSpaceBG()
. = ..()
. = ..()

Entered() and Exited() don't return anything.

You don't need to set the retval. You can just call ..()

If a function doesn't return a value, use ..()

If a function does return a value, use . = ..() or return ..() depending on your control path.
In response to Ter13
Alright, fair enough.
One thing I'm curious about though.. After I log in again, the image is gone, and as I move around it isn't there either, until I leave the area and enter it again.

I can think of a couple ways to fix this, but is there a simple way to do it?
Entered() and Exited() aren't called if you set a movable's loc manually.

This is why I recommend using a ForceMove() function rather than ever doing loc = locate() for any movable that will ever be involved in an Crossed()/Uncrossed()/Entered()/Exited() call during its lifestyle.

This is a very robust ForceMove() implementation:

#define clamp(v,l,h) min(max(v,l),h)
#define floor(x) round(x)
#define ceil(x) (-round(-(x)))
#define TILE_WIDTH 32
#define TILE_HEIGHT 32

atom
movable
proc
//Force the movable to move to a set location. Will call Crossed/Uncrossed/Entered/Exited, but not Cross/Uncross/Enter/Exit
ForceMove(atom/NewLoc,Dir=0,Step_x=0,Step_y=0)
var/worldx
var/worldy
if(NewLoc)
worldx = clamp(NewLoc.x*TILE_WIDTH + Step_x,TILE_WIDTH,(world.maxx+1)*TILE_WIDTH)
worldy = clamp(NewLoc.y*TILE_HEIGHT + Step_y,TILE_HEIGHT,(world.maxy+1)*TILE_HEIGHT)
if(Step_x<0||Step_x>TILE_WIDTH||Step_y<0||Step_y>TILE_HEIGHT)
NewLoc = locate(worldx/TILE_WIDTH,worldy/TILE_HEIGHT,NewLoc.z)
Step_x = worldx-NewLoc.x*TILE_WIDTH
Step_y = worldy-NewLoc.y*TILE_HEIGHT
else
Step_x = 0
Step_y = 0
var/curx = x*TILE_WIDTH+step_x+bound_x
var/cury = y*TILE_HEIGHT+step_y+bound_y
if(loc==NewLoc&&curx==worldx&&cury==worldy)
dir = Dir||dir
return

var/atom/OldLoc = loc
var/list/olocs = locs, list/olaps = list(), list/oareas = list()
var/list/nlocs = list(), list/nlaps = list(), list/nareas = list()
if(istype(OldLoc,/turf))
olaps = obounds(src) - olocs
for(var/turf/t in olocs) oareas |= t.loc
loc = NewLoc
step_x = Step_x
step_y = Step_y
dir = Dir||dir
nlocs = locs
if(istype(NewLoc,/turf))
nlaps = obounds(src) - nlocs
for(var/turf/t in nlocs) nareas |= t.loc

var/xlocs = olocs - nlocs, xlaps = olaps-nlaps, xareas = oareas-nareas
nlocs -= olocs; nlaps -= olaps; nareas -= oareas
for(var/atom/movable/o in xlaps) o.Uncrossed(src)
for(var/atom/o in xlocs) o.Exited(src,loc)
for(var/area/a in xareas) a.Exited(src,loc)
for(var/area/a in nareas) a.Entered(src,OldLoc)
for(var/atom/o in nlocs) o.Entered(src,OldLoc)
for(var/atom/movable/o in nlaps) o.Crossed(src)

//override New() to consider initialization a form of movement
New()
if(loc)
if(isturf(loc))
var/list/areas = list()
for(var/turf/t in locs)
areas |= t.loc
for(var/area/a in areas)
a.Entered(src)
for(var/turf/t in locs)
t.Entered(src)
for(var/atom/movable/o in obounds(src))
o.Crossed(src)
else
loc.Entered(src)


Using ForceMove() rather than manually setting player loc will prevent this issue from happening.

If you ever set the player's location manually, you will wind up with Entered()/Exited()/Crossed()/Uncrossed() being unreliable, which makes your job as a programmer a lot harder/messier.
Oh damn, that's awesome. I figured that was the problem, when I load the character I use locate() and set it's loc directly. Same goes for admin commands. I'm going to dissect that and try to implement it, thanks a lot man. I came up with a short term solution:

if(istype(loc.loc, /area/Space)) client.addSpaceBG()


I added that to the character loading. I do realize I should use what you posted for the long term, though. So I'll definitely use it.
I'm going to dissect that and try to implement it, thanks a lot man. I came up with a short term solution:

There are two ways to write software. Make a program so simple that it has zero bugs, or make a program so complex that it has zero bugs. The former is by far the harder of the two.

Ironically, you'd think I was calling my solution the complex one because your solution is one line. I disagree. My solution is the simpler one because it avoids causing the bug in the first place.

;P
Touche