ID:1949983
 
(See the best response by FKI.)
Code:
     Travel()
AngleDir()
spawn(0.25)
if(src)
Translate(cos(angle)*Speed,sin(angle)*Speed)
Layer()
if(round(Duration)<=0)
del src
else
Duration*=0.99
Travel()


Problem description:

When projectiles call the procedure listed above, they take up A LOT of cpu. I'm not sure of a more efficient way to handle their movement, but I know recursive calls of the proc isn't the answer.




The procedure was called 3720 times and I only fired one beam.
note: I'm also using Kaiochao's absolute positions library to translate the angles.
There's no reason this is using recursion.

Travel()
while(loc)
AngleDir()
Translate(cos(angle)*Speed,sin(angle)*Speed)
Layer()
if(round(Duration)<=0)
loc = null
del src
else
Duration *= 0.99
sleep(TICK_LAG)


Proc call overhead is going to be a big deal here. Consider inlining AngleDir() and Layer().

Also, you might consider simplifying how you track duration. I'd recommend storing it in world ticks, not multiplying by a float. Simply subtracting a world tick every frame will be a lot faster. You could also store the current angle in x/y component using sin/cos when the angle is actually changed, thus meaning if the angle doesn't change, sin()/cos() don't need to be called every frame.
I'm also using Kaiochao's absolute positions library to translate the angles.

Kaiochao's absolute positions library has an awful lot of proc call overhead because Px()/Cx(), etc are called as procs rather than inlined defines. I've talked to him about fixing that, but he's very resistant to that suggestion.
In response to Ter13
Ter13 wrote:
There's no reason this is using recursion.

> Travel()
> while(loc)
> AngleDir()
> Translate(cos(angle)*Speed,sin(angle)*Speed)
> Layer()
> if(round(Duration)<=0)
> loc = null
> del src
> else
> Duration *= 0.99
>

Proc call overhead is going to be a big deal here. Consider inlining AngleDir() and Layer().

Also, you might consider simplifying how you track duration. I'd recommend storing it in world ticks, not multiplying by a float. Simply subtracting a world tick every frame will be a lot faster. You could also store the current angle in x/y component using sin/cos when the angle is actually changed, thus meaning if the angle doesn't change, sin()/cos() don't need to be called every frame.

while(Firing)
sleep(0.5)
Cooldown=1
Cooldown()
a:HitStun(0.03)
flick("Beam",a)
projectile(a,/obj/projectile/,,Angle)
Beaming--


There's a rough interpretation of how beams are handled. The 'projectile()' procedure is called x(length of the beam) amount of times. When using a while loop in Travel(), each piece comes out only after previous one is deleted.
In response to Ter13
Best response
Ter13 wrote:
I'm also using Kaiochao's absolute positions library to translate the angles.

Kaiochao's absolute positions library has an awful lot of proc call overhead because Px()/Cx(), etc are called as procs rather than inlined defines. I've talked to him about fixing that, but he's very resistant to that suggestion.

I had already done some #define changes of my own and went ahead and finished after reading this.

//  Kaiochao
// 11 Jan 2014
// Absolute Positions
// Last update: 7 Oct 2014

// This library is a set of procs for finding, setting,
// and shifting the absolute pixel positions of atoms.

// (0, 0) is the bottom-left pixel of the map.
// You can get the other corners using the map_width() and map_height() procs
// from the map info library that this one uses.

// modifications by FKI (9/27/2015)
#define ismovable(a) (istype(a, /atom/movable))

#ifndef TILE_SIZE
#define TILE_SIZE 32 // change this as necessary
#endif

#define TILE_WIDTH (TILE_SIZE) // change this as necessary
#define TILE_HEIGHT (TILE_SIZE) // change this as necessary

#define Px(a, Pc) (ismovable(a)) ? ((a:x - 1) * TILE_WIDTH + a:bound_x + a:step_x + Pc * a:bound_width) : ((a:x - 1 + Pc) * TILE_WIDTH)
#define Py(a, Pc) (ismovable(a)) ? ((a:y - 1) * TILE_HEIGHT + a:bound_y + a:step_y + Pc * a:bound_height) : ((a:y - 1 + Pc) * TILE_HEIGHT)

#define Cx(a) (Px(a, 0.5))
#define Cy(a) (Py(a, 0.5))

atom
//proc
// The absolute coordinates of this atom's bottom-left corner,
// + a percentage of the atom's dimensions.
//Px(Pc) return (x - 1 + Pc) * tile_width()
//Py(Pc) return (y - 1 + Pc) * tile_height()

// The absolute coordinates of this atom's center.
//Cx() return Px(0.5)
//Cy() return Py(0.5)


movable
// Movables require a little bit extra, compared to statics.
//Px(Pc) return (x - 1) * tile_width() + bound_x + step_x + Pc * bound_width
//Py(Pc) return (y - 1) * tile_height() + bound_y + step_y + Pc * bound_height


var tmp
// Accumulated decimal parts.
__dec_x
__dec_y

// The direction to the last object bumped.
bump_dir = 0

// This allows you to use decimal values for StepX and StepY
// when you call Move().
Move(Loc, Dir, StepX, StepY)
var rx = round(StepX)
var ry = round(StepY)
__dec_x += StepX - rx
__dec_y += StepY - ry
while(__dec_x < -0.5) { __dec_x ++; rx -- }
while(__dec_x >= 0.5) { __dec_x --; rx ++ }
while(__dec_y < -0.5) { __dec_y ++; ry -- }
while(__dec_y >= 0.5) { __dec_y --; ry ++ }
var dx1 = rx - step_x
var dy1 = ry - step_y
var cx = Cx(src)
var cy = Cy(src)
. = ..(Loc, Dir || dir, rx, ry)
var dx2 = Cx(src) - cx
var dy2 = Cy(src) - cy
bump_dir = 0
if(dx2 != dx1) bump_dir |= dx1 > 0 ? EAST : WEST
if(dy2 != dy1) bump_dir |= dy1 > 0 ? NORTH : SOUTH

proc
SetLoc(atom/Loc, StepX, StepY)
loc = Loc
if(!isnull(StepX) && !isnull(StepY))
step_x = StepX
step_y = StepY

// Set the bottom-left corner to an absolute pixel position.
// Like Px() and Py(), you can set the position of a point that is
// a percentage of the object's dimensions from its corner.
SetPosition(X, Y, Z, Pcx, Pcy)
if(isloc(X))
var atom/a = X
X = Px(a, 0)
Y = Py(a, 0)
Z = a.z
if(Pcx) X -= Pcx * bound_width
if(Pcy) Y -= Pcy * bound_height
if(isnull(Z)) Z = z
loc = null
if(Z)
var tile_width = TILE_WIDTH
var new_x = X / tile_width + 1
if(new_x in 0 to world.maxx + 1)
var tile_height = TILE_HEIGHT
var new_y = Y / tile_height + 1
if(new_y in 0 to world.maxy + 1)
SetLoc(
locate(new_x, new_y, Z),
X % tile_width - bound_x,
Y % tile_height - bound_y)
__dec_x = X - round(X)
__dec_y = Y - round(Y)

// Set the center of this mover to an absolute pixel position.
SetCenter(X, Y, Z)
if(isloc(X))
var atom/a = X
X = Cx(a)
Y = Cy(a)
Z = a.z
SetPosition(X, Y, Z, 0.5, 0.5)

// Shift the mover's position by a certain in the x and y axes,
// taking obstacles into account along the line of movement.
// It's important to set the step_size to something that isn't
// smaller than the move itself, so that the move will be a shift
// rather than a jump.
Translate(Dx, Dy, Dir = 0)
if(!(Dx || Dy)) return

// also accepts Translate(list(Dx, Dy), Dir)
if(!isnum(Dx) && istype(Dx, /list))
Dir = Dy
Dy = Dx[2]
Dx = Dx[1]

// Force a "slide" instead of a "jump"
// see DM Reference: Move (movable atom)
var pre_step_size = step_size
step_size = max(abs(Dx), abs(Dy)) + 1
. = Move(loc, Dir, step_x + Dx, step_y + Dy)
step_size = pre_step_size

// Shift the mover's position by a certain distance and angle.
// Angle increases clockwise from NORTH.
// It's convenient for projectiles, but you would actually
// be better off storing a vector to Translate() by, in the
// case of an object moving with a constant velocity
// every frame.
Project(Distance, Angle, Dir = 0)
if(!Distance) return
return Translate(Distance * sin(Angle), Distance * cos(Angle), Dir)

In response to FKI
With the define additions, cpu usage has dropped to about 8-10 with single beams and even clashes.

I've even managed to make 8 characters fire off simultaneously for a peak of about 35 fluctuating cpu.



Thank you both! I'm really liking how it's running. However, if possible, any more ideas on how I can cut down on usage or is this about as low as I can go?
In response to Ter13
Ter13 wrote:
I'm also using Kaiochao's absolute positions library to translate the angles.

Kaiochao's absolute positions library has an awful lot of proc call overhead because Px()/Cx(), etc are called as procs rather than inlined defines. I've talked to him about fixing that, but he's very resistant to that suggestion.

Yeah, proc call overhead isn't trivial and it's darn near impossible to cut down on the backend. Inlining frequently called stuff is always better. Anything that can be easily handled with #define, should.
Huh... So many past problems I've encountered with CPU usage just suddenly started making sense. Damn. Glad I checked out this thread.

Also, that 8 way beam struggle looks pretty cool. I hope the character base is temporary though.

I'm not sure what else can be done for efficiency, aside from what was already mentioned about storing things, but usually in my experience things like this boils down to that, object pooling, using images or animate() whenever possible, and apparently proc call overhead.
In response to Toddab503
Toddab503 wrote:
Also, that 8 way beam struggle looks pretty cool. I hope the character base is temporary though.
It is. I haven't bothered getting an artist, because I tend not to finish projects. Don't want to make anyone work if I lose motivation.

Toddab503 wrote:
I'm not sure what else can be done for efficiency, aside from what was already mentioned about storing things, but usually in my experience things like this boils down to that, object pooling, using images or animate() whenever possible, and apparently proc call overhead.

How does object pooling work?
In response to Bl4ck Adam
That is some awesome improvements. Sounds even better when put into a percentile! :)
Going from 2 characters firing to 8 would be certain percentage of improvement that I cannot figure out at the moment...haha..alcohol!
In response to Bl4ck Adam
Bl4ck Adam wrote:
It is. I haven't bothered getting an artist, because I tend not to finish projects. Don't want to make anyone work if I lose motivation.

Ah, that's good and perfectly understandable! It would be awesome if more people could take that approach.

Bl4ck Adam wrote:
How does object pooling work?

Well, object pooling is basically creating a list of objects that you would be frequently creating and deleting, then instead grabbing them from said list, and resetting/readding them to the list when you're finished.

This doesn't make as big of a difference as it used to, but it can be less intensive than heavily using new() and del() last I checked.


Edit: With object pooling, you would usually create 100, 1000, or however many in world/New() to populate the list and then not ever have to create anymore.
In response to Bl4ck Adam
Bl4ck Adam wrote:
How does object pooling work?

http://www.byond.com/developer/Ter13/ObjectPools
I didn't mean to steal Ter's credit; his advice should still rightfully be taken into consideration, as there's still a lot of room for improvement in the OP's design.
GreatPirateEra wrote:
Suggestion: give the clashes a time limit. Like 3 seconds. Nobody wants to stay there forever. When the time is up, figure out which player had advanced more, and blast the loser away. If they were equal, make a explosion on the center and blast them both away

Thanks for the input, but that isn't what I needed! Clashes don't last forever either; they're handled.
In response to Ter13
Ter13 wrote:
There's no reason this is using recursion.

> Travel()
> while(loc)
> AngleDir()
> Translate(cos(angle)*Speed,sin(angle)*Speed)
> Layer()
> if(round(Duration)<=0)
> loc = null
> del src
> else
> Duration *= 0.99
> sleep(TICK_LAG)
>

Proc call overhead is going to be a big deal here. Consider inlining AngleDir() and Layer().

Also, you might consider simplifying how you track duration. I'd recommend storing it in world ticks, not multiplying by a float. Simply subtracting a world tick every frame will be a lot faster. You could also store the current angle in x/y component using sin/cos when the angle is actually changed, thus meaning if the angle doesn't change, sin()/cos() don't need to be called every frame.


I'm bringing this post back briefly. I've looked into adjusting the Travel() procedure to fit more closely to what you've laid out for me. The only problem I had while running it was related to beams. Presently, beams are just multiple projectiles.

Beam()
while([beam duration])
sleep([time here])
spawnProjectile()


The code doesn't quite look like that, but it functions pretty similar.

Since the Travel() procedure sleeps, the beam (or any other massive swarm of) projectiles can't spawn until the one before it is deleted. A remedy in the beam's case would be to have a single projectile and scale it up/down to match the beam's supposed length [probably], but in the case of any other rapid firing projectile, how would I handle the spawning?