Array of Size 4 Max In Functions Related to GUI

This is a bit of a doozy and apologies in advance that I don’t have a cleaner code to post as an example…

I am making a game. In this game, you are a spaceship that shoots lasers. When you fire a laser (SPACE), your sprite’s coordinates are put in an array and the Y value is set to decrement by whatever the value of laserSpeed is in an animated drawFunc

This array needs to remove all items in which the Y value is outside of the parent window bounds. I have at the bottom of this code an arrayLim routine that gets rid of the bad values, cleaning up the array. However, the net result is that somewhere, something is limiting the size of any arrays that are spawning things like lasers or enemies to size 4.

The star array on the other hand is much larger than 4 and essentially does the same thing that the laser array and enemy arrays are, the difference being that the array size is constant, no adding or removing items.

I’ll post the whole code at the end but highlight the relevant parts above. Again, apologies in advance.

Whether I’m calling .select from the drawFunc or just as a routine, something seems to limit the array to size 4.

	arrayLim = {loop
		{0.1.wait; 
			laserArray = laserArray.select({
				arg item, i; 
				item.at(1)>laserLen.neg
			}); 
			enemyArray = enemyArray.select({
				arg item, i; 
				win.view.bounds.contains(item.at(0) @ item.at(1))
	});
	}
	}.fork(AppClock);

Full code:

(
s = Server.local;
s.options.sampleRate_(44100);
AppClock.clear;
s.waitForBoot({
	var title = "SuperSpaceCollider";
	var win = Window.new(title, Window.availableBounds, false)
	.front
	.onClose_({Window.closeAll; s.quit; AppClock.clear; SystemClock.clear; ~enemySpawn.stop; ~rotate.stop});

	var winW = win.view.bounds.width, winH = win.view.bounds.height, arrayLim;
	
	var enemyArray = Array(40);

	var keycodes = Platform.case(
		//<-, ->, ↓, ↑
		\osx, {[123,124,125,126,49]},
		\linux, {[65361,65363,65364,65362,32]},
		\windows, {[37,39,40,38,32]}
	);

	//colors

	var spriteSize=36, maxRadius = 1.0;

	var playerX=winW/2-spriteSize, playerY=win.view.bounds.height-(spriteSize*2);

	var laserX = playerX, laserY = playerY, shipScale=1, laserWidth = spriteSize/12;

	var board = UserView(win, win.view.bounds)
	.background_(Color.blue(0.1));

	//more vars

	var activeLaser = Array(40), canFire=true, laserPos, radius = spriteSize/2;

	var starSpeed = 20;

	var movementSpeed = 8, enemySpeed = 6, enemyTestRect;

	var laserArray = Array(40);

	var laserSpeed = movementSpeed*4, laserLen = spriteSize/2.4;

	var playerDir = Set(1);

	var enemyPos = 50.collect({[win.view.bounds.width.rand, (win.view.bounds.height).rand.neg]});

	var enemyX = win.view.bounds.width.rand, enemyY=0, enemyRectArray;

	var cannonWidth = spriteSize/12, cannonLength=0.6*spriteSize;

	~flashAlpha = 0.0;
	~flashSize = 0;
	~fireDelay = 0.25;

	~frameRate = 30;

		//sounds
	
	~lfoBus = Bus.control(s,1);
	~revIn = Bus.audio(s,1);

	s.sync;


SynthDef(\engine, {
		arg freq=80, amp=0.5, gate=1, pan=0.5, filtFreq=1500, in=0, out=0;
	var sig, env, detune, pitch, width, noise;
	detune = 3.collect({0.5.rand2.midiratio});
		width = LFNoise1.ar(LFNoise0.ar.range(1.0,4.0)).range(0.01,0.99);
	pitch = freq*detune;
		noise = WhiteNoise.ar(0.18);
		noise = RLPF.ar(noise, 2000, 0.8);
		sig = LFPulse.ar(pitch, 0, width, 1/6);
	env = EnvGen.ar(Env.asr, gate, doneAction:2);
		sig = HPF.ar(sig, 500);
		sig = Mix.ar(sig);
		sig = Mix.ar([sig, noise]);
		sig = BPF.ar(sig, LFNoise1.ar(2).range(800,1000));
		sig = RLPF.ar(sig, 500, 0.5);
		sig = sig*amp*env;
		// sig = LeakDC.ar(sig);
	sig = Pan2.ar(sig, pan);
	Out.ar(out, sig);
}).add;

	SynthDef(\pow, {
		arg freq1=2000, freq2=20, dur=0.3, pan=0.5, gate=1, amp = 0.3, out=0;
		var sig, env, line;
		line = XLine.ar(freq1,freq2,dur);
		sig =
		10.collect({
			arg i;
			LFPulse.ar((line*(1.rand2).midiratio), 0)/5
		});
		sig = Mix.ar(sig).fold(-1.0,1.0);
		env = EnvGen.ar(Env.perc(0.01,dur*2), gate, doneAction:2);
		sig = BPF.ar(sig);
		sig = (sig * amp * env);
		sig = Pan2.ar(sig, pan);
		Out.ar(out, sig);
	}).add;



SynthDef(\explosion, {
	arg amp=0.6, pan=0, freq=30, noiseAmp=0.16, noiseAmp2=0.5, lpfFreq=1748, hpfFreq=40, dur=3, out=0;
	var sig, env, noise, fold;
		noise = Mix.ar([ClipNoise.ar(noiseAmp), BrownNoise.ar(noiseAmp2)]);
	sig = 7.collect({
		LFPulse.ar(LFNoise1.ar.range(21.0,35.0), 1.0.rand, LFNoise1.ar(1).range(0.01,0.99));
	});
	fold = EnvGen.ar(Env([0.1,0.8],[0.4],-4.0.rand));
	sig = Splay.ar(sig);
	sig = Mix.ar([sig,noise]);
	sig = sig.fold(fold.neg,fold);
	sig = sig.round(2.pow(-4.rand));
	env = EnvGen.ar(Env.perc(0.001,dur), doneAction:2);
	sig = sig.clip(-1,1);
		sig = CombL.ar(sig, 0.2, 0.2, 1.0);
	sig = sig*env*amp;
	sig = LPF.ar(sig, lpfFreq);
	sig = HPF.ar(sig, hpfFreq);
	sig = LeakDC.ar(sig);
	sig = Pan2.ar(sig, pan);
	Out.ar(out, sig);
}).add;
	
SynthDef(\alien, {	
	arg amp=1, pan=0, freq=60, detun=0.5, fFreqLo=20, fFreqHi=1500, dur=5, depth=0, out=0;
	var sig, env, filtMod, lfo;
		filtMod = In.kr(~lfoBus).exprange(fFreqLo, fFreqHi);
	
	env = EnvGen.ar(Env([0,1,0], [0,dur,0]), doneAction:2);
	sig = 7.collect({
		LFSaw.ar(freq*rand2(detun).midiratio, 4.0.rand)/7;
	});
	sig = Mix.ar(sig);
	sig = RLPF.ar(sig, filtMod, 0.3);
	sig = Pan2.ar(sig, pan);
	sig = sig * amp * env;
	Out.ar(out, sig);
}).add;
	
SynthDef(\key, {
	arg freq=120, amp=0.5, pan=0, rel=1.0;
	var sig, env;
	env = EnvGen.ar(Env.perc(0.001, rel), doneAction:2);
	sig = LFTri.ar(freq);
	sig = Pan2.ar(sig, pan);
	sig = sig*amp*env;
	Out.ar(~revIn, sig);
}).add;	
	
SynthDef(\verb, {
	arg in=~revIn, out=0, mix=0.33, room=0.5, damp=0.5;
	var sig;
		sig = In.ar(~revIn);
		sig = FreeVerb.ar(sig, mix, room, damp);
	Out.ar(out, sig);
}).add;	
	
SynthDef(\lfo, {
		arg cpm = 440, out=~lfoBus;
		var freq = cpm/60, sig;
		sig = LFTri.kr(freq);
		Out.kr(~lfoBus, sig);
	}).add;
		
		
	s.sync;	
	
	Synth(\lfo);


//draw stars
~starArray = 500.collect({
		x = win.view.bounds.width.rand;
z = win.view.bounds.height.rand;
	r = exprand(0.3,2.0);
	[[x,z,r],[x,(win.view.bounds.height.neg+z),r]];
}).flatten(1);

	~starY = 0;

	~stars = UserView(win, win.view.bounds)
.animate_(true)
	.clearOnRefresh_(true)
	.frameRate_(~frameRate/2)
.drawFunc_({
	~starY = (~starY+1).wrap(0,win.view.bounds.height);
	Pen.translate(0, ~starY);
		Pen.use({
	~starArray.do({
	arg item;
	Pen.addOval(Rect(
			item.at(0), item.at(1),
			item.at(2), item.at(2)
		)
		)
		.color_([Color.grey(exprand(0.8,1.0)), Color.white].wchoose([0.1,0.8]))
		.fill;
			});
});
});


	~engineSynth = Synth(\engine);
	
	

	s.sync;

	// ~powSynth = {Synth(\pow, [\pan, playerX.linlin(0, win.view.bounds.width,-0.9,0.9)])};

	~laserSynth = {canFire = false; Synth(\pow, [\pan, playerX.linlin(0, win.view.bounds.width,-0.9,0.9)]);};

	~laserView = UserView.new(~stars, win.view.bounds)
.animate_(true)
.drawFunc_({
	laserArray.collectInPlace({
		arg item, i;
		var laserHitBox;
		x = item.at(0);
		y = item.at(1);
			y = y-laserSpeed;
			laserHitBox = Rect.fromPoints(x+(cannonWidth/2) @ (y+cannonLength), x+spriteSize-(cannonWidth/2) @ (y+spriteSize));
			2.do({
				arg i;
				Pen.line([laserHitBox.rightTop, laserHitBox.leftTop].at(i), [laserHitBox.rightBottom, laserHitBox.leftBottom].at(i));
			});
			Pen.width_(cannonWidth).capStyle_(1).color_(Color.cyan).stroke;
			[x, y];
		});
		~hitBox = laserArray.collect({
			arg item, i;
		x = item.at(0);
		y = item.at(1);
			Rect.fromPoints(x+(cannonWidth/2) @ (y+cannonLength), x+spriteSize-(cannonWidth/2) @ (y+spriteSize))
		});
});

	~playerSprite = UserView(~stars, win.view.bounds)
	.animate_(true)
	.frameRate_(~frameRate)
	.drawFunc_({
		var cockpitH = spriteSize/3, cockpitW = spriteSize/7;
		var numLines = 4;
		var propulsionSize = rrand(15.0,20.0);
		var h = spriteSize/2, delta=rrand(0.1, 0.3)*spriteSize/6, height = exprand(0.4,(1/4))*spriteSize, tip = h+rand2(delta/2)@(spriteSize+height);
		//movement
		playerDir.do({
			arg item;
			case
			{item == (\left)} {playerX=playerX-movementSpeed}
			{item == (\right)} {playerX=playerX+movementSpeed}
			{item == (\down)} {playerY=playerY+movementSpeed}
			{item == (\up)} {playerY=playerY-movementSpeed};
		});
		playerY=playerY.clip(0, win.view.bounds.height-spriteSize);
		playerX=playerX.clip(0, win.view.bounds.width-spriteSize);
		Pen.translate(playerX, playerY);

		//draw

		//propulsion
		Pen.use({
		Pen
		.moveTo((spriteSize/2)-(spriteSize/15) @ (spriteSize))
		.quadCurveTo((spriteSize/2)+(spriteSize/15) @ (spriteSize), (tip.x) @ (tip.y*(pi/2).sqrt))
		.lineTo((spriteSize/2)-(spriteSize/15) @ (spriteSize))
		.fillAxialGradient((spriteSize/2) @ (spriteSize), (spriteSize/2) @ (tip.y), Color.cyan(1,1.0), Color.cyan(1.0,0));

		Pen
		.addOval(Rect.aboutPoint(spriteSize/2 @ (spriteSize), spriteSize/propulsionSize, spriteSize/propulsionSize)).color_(Color.cyan).fill;
		});

		2.do{
			arg i;
			Pen
			.moveTo(h + [delta.neg, delta].at(i) @ spriteSize)
			.quadCurveTo(tip, h + [delta.neg, delta].at(i) @ (spriteSize+delta.rand))
			.lineTo(h + [delta, delta.neg].at(i)@spriteSize)
			.fillAxialGradient(tip, h @ (spriteSize-delta), Color.blue, Color.cyan(1.5))
		};
		Pen.addOval(Rect.aboutPoint(h@spriteSize, delta, delta)).fillAxialGradient(tip, h @ (spriteSize-delta), Color.cyan, Color.white);
Pen.use({
		//wings

			Pen.use({
		2.do({
			arg i;
			var ratio = (5/8);
			Pen
			.moveTo(spriteSize/2 @ (0.875*spriteSize))
			.lineTo(i*spriteSize @ spriteSize)
			.lineTo(i*spriteSize @ (15*spriteSize/16))
			.quadCurveTo(spriteSize/2 @ (spriteSize/4.neg), spriteSize/2 @ (spriteSize*ratio))
			.fillColor_(Color.grey(0.3))
			.strokeColor_(Color.grey(0.2))
			.fill
		});

		//decor
		numLines.do({
			arg i;
			Pen
			.line(spriteSize/numLines*(i+0.5) @ spriteSize, spriteSize/numLines*(i+0.5) @ (7*spriteSize/10))
			.strokeColor_(Color.grey)
			.width_(spriteSize/18)
			.stroke
		});
		});

		~flashAlpha = ~flashAlpha - (0.1);


			//cannon flash
			2.do({
				arg i;
			~flashSize = exprand(spriteSize/12.0, spriteSize/24.0);
			Pen
			.addOval(~flashRect = Rect.aboutPoint(((i*spriteSize)+[(cannonWidth/2),(cannonWidth/2).neg].at(i))@((0.6*spriteSize)-(~flashSize)), ~flashSize, ~flashSize*1.5) )
			.color_(Color.cyan(1.8, ~flashAlpha))
			.fillAxialGradient(((i*spriteSize)+[(cannonWidth/2),(cannonWidth/2).neg].at(i))@(cannonLength), ((i*spriteSize)+[(cannonWidth/2),(cannonWidth/2).neg].at(i))@(cannonLength-(~flashSize*2)),Color.white.alpha_(~flashAlpha), Color.cyan(1,0.5*~flashAlpha));
			});

Pen.use({

		//cannons
		2.do({
			arg i;
			Pen
				.line(((i*spriteSize)+[(cannonWidth/2),(cannonWidth/2).neg].at(i))  @ spriteSize, ((i*spriteSize)+[(cannonWidth/2),(cannonWidth/2).neg].at(i)) @ (cannonLength))
			.strokeColor_(Color.grey)
			.width_(cannonWidth)
			.stroke
		});

		//body
		2.do({
			arg i;
			Pen
			.moveTo((spriteSize/2) @ 0)
			.quadCurveTo(spriteSize/2 @ (spriteSize), spriteSize/4+(i*spriteSize/2) @ (spriteSize/2))
			.width_(spriteSize/36)
			.fillAxialGradient(spriteSize/2 @ 0, spriteSize/2 @ spriteSize, Color.grey(0.6), Color.grey(0.3))
		});

		//jet

		Pen
		.addRoundedRect(~jetRect = Rect.aboutPoint(spriteSize/2 @ (0.9*spriteSize), spriteSize/15, spriteSize/8), ~jetRect.width/2, ~jetRect.height/4)
		.fillAxialGradient(~jetRect.left@(~jetRect.height/2), ~jetRect.right@(~jetRect.height/2), Color.grey(0.7), Color.grey(0.5));


		//cockpit
		Pen.addOval(Rect(spriteSize-cockpitW/2, (0.4*spriteSize), cockpitW, cockpitH))
		.fillAxialGradient(spriteSize-cockpitW/2 @ (4*spriteSize/10), spriteSize-(cockpitW/2+cockpitW) @ ((4*spriteSize/10)+cockpitH), Color.white, Color.cyan(0.5))
		.strokeColor_(Color.grey(0.7))
		.width_(2)
		.stroke;
		});
		});
	});


	~fireFunc = {
		{canFire = false;
		~laserSynth.value;
		laserArray.add([playerX, playerY+laserLen]);
		~flashSize = (spriteSize/24.0);
		~flashAlpha = 1.0;
		0.25.wait;
		canFire=true;
	}.fork(AppClock)
	};

	View.globalKeyDownAction_({
		arg view, char, mod, uni, keycode;
		case
		{keycode == keycodes[0]} {playerDir.add(\left);}
		{keycode == keycodes[1]} {playerDir.add(\right);}
		{keycode == keycodes[2]} {playerDir.add(\down);}
		{keycode == keycodes[3]} {playerDir.add(\up);}
		{keycode == keycodes[4] && canFire==true} {~fireFunc.value}

		// {mod.isShift} {movementSpeed = 16}
		{uni == 27} {win.close; win.refresh};
	});
	View.globalKeyUpAction_({
		arg view, char, mod, uni, keycode;
		case
		{keycode == keycodes[0]} {playerDir.remove(\left);}
		{keycode == keycodes[1]} {playerDir.remove(\right);}
		{keycode == keycodes[2]} {playerDir.remove(\down);}
		{keycode == keycodes[3]} {playerDir.remove(\up);}
		{keycode == keycodes[4]} {}
	});

~enemySpawn = fork {
	loop { var x = rrand(winW-spriteSize, spriteSize);
			enemyArray.add([x, 0]);
			Synth(\alien, [\dur, ~dur*enemySpeed, \amp, 0.125, \pan, x.linlin(radius, winW-radius, -1.0, 1.0)]);
		rrand(2.0,0.5).wait;
	}
};
	
		~dur = winH/~frameRate/60;
~rot=0;
	~rotationSpeed = 10;
	~enemySpriteFx = {loop{~rot = ~rot + ~rotationSpeed; (1/30).wait;}}.fork;
~enemies = UserView(~stars, win.view.bounds)
.animate_(true)
.drawFunc_({
	enemyArray = enemyArray.collect({
			arg inval;
			var x = inval.at(0), y = inval.at(1);
			y = y+enemySpeed;
			[x, y];
		});
		// enemyArray = enemyArray.select({
		// 	arg inval;
		// 	var x = inval.at(0), y = inval.at(1);
		// 	y<(winH+(spriteSize/2));
		// });
		enemyRectArray = enemyArray.collect({
			arg inval;
			var x = inval.at(0), y = inval.at(1);
			Rect.aboutPoint(x@y, spriteSize/2, spriteSize/2);
		});
	enemyRectArray.do{
		arg inval;
		var cockpit = Rect.aboutPoint(inval.center, radius/2, radius/2);
			Pen.color_(Color.grey);
		5.do{
			arg i;
			Pen.addAnnularWedge(inval.center, cockpit.height/2, radius, ((360/5)*i+~rot).degrad, 46.degrad)
				.color_(Color.red(1.0, (1/5)*i)).fill;
		};
			Pen.addOval(inval).fillRadialGradient(inval.center, inval.center.x@inval.top, 0.4*radius, radius, Color.clear, Color.grey(0.4));
			Pen.addOval(cockpit).fillRadialGradient(cockpit.rightTop, cockpit.center, cockpit.height/2, cockpit.height, Color.grey(0.9), Color.grey(0.1));
		};
});
	
	arrayLim = {loop
		{0.1.wait; 
			laserArray = laserArray.select({
				arg item, i; 
				item.at(1)>laserLen.neg
			}); 
			enemyArray = enemyArray.select({
				arg item, i; 
				win.view.bounds.contains(item.at(0) @ item.at(1))
	});
	}
	}.fork(AppClock);
	
	win.refresh;
});
)

There’s a very important note at the beginning of the Array help file, which explains the 4 limit.

NOTE: For Arrays, the add method may or may not return the same Array object. It will add the argument to the receiver if there is space, otherwise it returns a new Array object with the argument added. Thus the proper usage of add with an Array is to always assign the result as follows:

z = z.add(obj);

hjh

People hit this (somewhat subtle, since it works “sometimes”) problem often enough that perhaps the compiler/interpreter could emit a warning when calling .add on an Array without reassigment. Not sure how easy it would be to detect that situation but I guess it would avoid some confusion and frustration.

It would also need to detect when the array was created with an insufficient size – this may be nontrivial in the compiler stage.

I think the second shouldn’t be flagged here.

// not OK, but 2.7 seconds on my machine :-O
// massive load on the garbage collector
(
bench {
	var a = Array.new;
	10000000.do { |i|
		a.add(i)
	};
}
)

// OK, and 0.905 seconds
(
bench {
	var a = Array.new(10000000);
	10000000.do { |i|
		a.add(i)
	};
}
)

hjh
1 Like

Array growth is geometric, so it’s much quicker if you do actually re-assign to a here! c.f.

(
bench {
	var a = Array.new;
	1e7.do { |i|
		a.add(i)
	};
    [a.size == 1e7, a.size].postln
} // [false, 1] 2.0sec
)

(
bench {
	var a = Array.new;
	1e7.do { |i|
		a = a.add(i)
	};
    [a.size == 1e7, a.size].postln
} // [true, 1e7] 0.8sec
)

There’s also List (not LinkedList!) which is a boxed array.

List.add works as you’d expect, and is fast, and may be a nicer option?

(
bench {
	var l = List.new;
	1e7.do { |i|
		l.add(i)
	};
    (l.size == 1e7).postln
} // [true, 1e7] 1.2sec
)

(
bench {
	var l = LinkedList.new;
	1e7.do { |i|
		l.add(i)
	};
    (l.size == 1e7).postln
} // [true, 1e7] 4.3sec
)

Ps. Geometric as in 4, 8, 16, 32, 64, 128…

(
var a = Array.new;
var k = a.maxSize;
1e4.do { arg i; a = a.add(i); (k != a.maxSize).if({ k = a.maxSize; k.postln })};
)

Of course – but the point of the example was to show what not to do, and why it’s bad.

hjh