About Monkey 2 › Forums › Monkey 2 Programming Help › Ported particle System "Memory access violation" [SOLVED]
This topic contains 8 replies, has 3 voices, and was last updated by 
 nobuyuki
 2 years ago.
- 
		AuthorPosts
 - 
		
			
				
March 27, 2017 at 10:54 am #7624
Hi, I did a quick port of https://github.com/nobuyukinyuu/argyne and put it here: https://github.com/Difference/argyneParticles4MX2
The example runs, but I keep getting random “Memory access violation” . I dont get why, because it happens after a null check.
Can anybody spot what I’ve done wrong? (I don’t quite get the output of the debugger.)
[EDIT]: To quickly reproduce the error, pres spacebar 2-5 times, whilst moving the mouse.
March 27, 2017 at 11:49 am #7628I’m thinking it may have something to do with that I changed the Clone() Methods from :Object to dedicated types?
March 27, 2017 at 9:39 pm #7633Ok, this one’s a bit fun as I’m finding it hard to reproduce here, but it does happen every now and then. What OS are you on? Windows 10 here.
Anyway, this code looks a bit dodgy to me, one of the ParticleEmitter ctors:
Monkey12345Method New(factory:ParticleFactory, interval:Int, ttl:Int = 500, LockParticlesToEmitter:Bool = False)Super.New(ttl, Self.initial, Self.last)...End MethodThis is invoking Super.New using ‘unconstructed’ fields of the super class as parameters, ie: Self.initial and Self.last (which are really members of BaseParticle). This is IMO kind of nonsense code, like Local k:=k or something.
These are *usually* null before super.Ctor is called, but apparently not always. Changing the ctor to:
Monkey12345Method New(factory:ParticleFactory, interval:Int, ttl:Int = 500, LockParticlesToEmitter:Bool = False)Super.New(ttl, Null, Null )...End Method…appears to fix it for me, although like I say it’s hard to reproduce here so I’m not 100% sure.
The ‘big fix’ is probably to (at least) disallow use of any base class members when evaluating parameters for use with Super.New calls, as it doesn’t make sense to allow use of yet-to-be-constructed members.
March 28, 2017 at 7:22 am #7640Great, fix confirmed working
(‘m on MacOS Sierra)
Well spotted!
I think implementing the “big fix” is worth looking into, although it is a logical error in the code, MX2 should probably catch it, especially because it is hard to reproduce, and only occurs randomly.
[EDIT] I guess Self.something could be a global, set to something and that case should be allowed…
PS: For anyone interested https://github.com/Difference/argyneParticles4MX2 is updated with the corrected version.
March 29, 2017 at 2:03 am #7650Hi!
Yes, it seems that bit of code from the original was a bit janky. At some point in the design of argyne, I ran into a problem where I couldn’t make interfaces generic (a limitation of Monkey at the time) and ended up having to rewrite a large portion of the system. This bug may have been an artifact of sloppy refactoring.
One of the reasons I abandoned argyne was because it creates a lot of object initializations and does nothing to track or pool particle resources, making the entire system very resource-intensive compared to the (as of yet still unreleased) system I wrote to replace it. Thrash was inevitable as the GC would have to sweep these eventually and that would cause big hiccups on Android (and eventually in glfw too if the example was anything to go by).
Thanks for updating argyne for mx2. If I were to rewrite it myself, it would probably handle a few things differently! The general way to set it up would probably stay similar, though…… and there’d also be a way to delay its startTime, and an easing curve option in ParticleValues.Set()…..
March 29, 2017 at 9:27 am #7655Hi nobuyuki, nice to hear from you on this.
(You are of course very welcome to clone back the MX2 port, and make your clone the new base version, I’ll delete mine if you do. )
I think MX2 could do with a good particle system, so I’m not done looking into whats out there, but for now, I’ll be using my animation system that has similarities with your particle system, in that it also tweens between positions, and such.
If you do release that other system, give a heads up! .-)
March 29, 2017 at 11:37 pm #7658I’d rather you keep your version available for the time being, since I don’t plan to do an authoritative version for mx2 anytime soon. I’m waiting for Jungle to get some mx2 support before diving into that, and still have a few incomplete projects that aren’t even using mojo2 so I’ll be stuck there for at least a while.
The other system is very simple and actually written less with public use in mind, but I’ll think about it! It follows a “god particle” baseclass pattern which I didn’t favor for argyne, and doesn’t have intrinsic support for frame-based timing or second-order spawning. What it does have is Easing curve functions and an allocator. Here’s a code snippet of the main class, it won’t work without the nscreen framework (unreleased) but it should be fairly simple to understand.
Monkey123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208'Particle system from FVA, with easing curve dependencies offloaded to nScreen2.Import mojoImport UsefulFunctionsImport nscreen'Summary: Allows the allocation of one type of particle.Class ParticleAllocator<T>Field data:T[]Field iterator:Int'Summary: Prepares the allocator.Method New(size:Int = 32)data = data.Resize(size)End Method'Summary: Initializes the allocator with default values.Method Init:Void()For Local i:Int = 0 Until data.Length()data[i] = New T()NextEnd Method'Summary: Returns a free particle.Method GetNext:T()Iterate()Return data[iterator]End Method'Summary: Manually moves the iterator forward.Method Iterate:Void(amt:Int = 1)iterator = Cycle(iterator + amt, 0, data.Length())End Method'Summary: Batch renders all particles in the allocator. WARNING: No type constraints, can crash at runtime! Be carefulMethod RenderAll:Void()For Local i:Int = 0 Until data.Length()data[i].Render()NextEnd Method'Summary: Batch updates all particles in the allocator. WARNING: No type constraints, can crash at runtime! Be carefulMethod UpdateAll:Void()For Local i:Int = 0 Until data.Length()data[i].Update()NextEnd MethodEnd Class'Summary: The base particle class. Most of the time you can use this...Class ParticleField img:ImageField Visible:Bool '= TrueField startTime:IntField ttl:Int = 1000Field loc:= New pVal2(New nsEaseCubic()) 'Initial and final Location from origin.Field scl:= New pVal(New nsEaseQuad()) 'ScaleField angle:= New pVal(New nsEaseLinear())Field alpha:= New pVal(New nsEaseLoopback(2))Method New()scl.Set(1, 1)alpha.Set(0, 1)startTime = Millisecs()End MethodMethod New(x:Float, y:Float, xx:Float, yy:Float, usePolar:Bool = False)Set(x, y, xx, yy, usePolar)startTime = Millisecs()End MethodMethod Update:Float()Local p:Float = 1If Visiblep = (Millisecs() -startTime) / float(ttl)If p > 1 Then Visible = Falseloc.Update(p)scl.Update(p)angle.Update(p)alpha.Update(p)End IfReturn pEnd MethodMethod Render:Void(xOffset:Float = 0, yOffset:Float = 0)If VisibleLocal a:Float = GetAlpha()SetAlpha(a * alpha.current)DrawImage(img, xOffset + loc.current.x, yOffset + loc.current.y, angle.current, scl.current, scl.current)SetAlpha(a)End IfEnd MethodMethod Reset:Void()loc.Set(0, 0, 0, 0)scl.Set(1, 1)angle.Set(0, 0)alpha.Set(0, 1)startTime = Millisecs()Visible = TrueEnd Method'Summary: Rotates and alters the length of the destination point.Method RotMulDest:Void(rotAmt:Float = 0, mulAmt:Float = 1.0)Local t:Float = ATan2(loc.last.x - loc.first.x, loc.last.y - loc.first.y) + rotAmtLocal r:Float = pVec.Dist(loc.first.x, loc.first.y, loc.last.x, loc.last.y) * mulAmtloc.last.x = loc.first.x + Sin(t) * rloc.last.y = loc.first.y + Cos(t) * rEnd MethodMethod Set:Void(x:Float, y:Float, xx:Float, yy:Float, usePolar:Bool)If usePolarloc.Set(x, y, x + Sin(xx) * yy, y + Cos(xx) * yy)Elseloc.Set(x, y, xx, yy)End IfEnd MethodEnd ClassClass pVecField x:Float, y:FloatMethod New(x:Float = 0, y:Float = 0)Self.x = x; Self.y = yEnd MethodMethod Mul:Void(percent:Float)x *= percent; y *= percentEnd MethodMethod Set:Void(x:Float, y:Float)Self.x = x; Self.y = yEnd MethodMethod Set:Void(p:pVec)x = p.x; y = p.yEnd Method'Summary: Sets a vec to values from polar coordinates.Function SetPolar:Void(v:pVec, theta:Float, r:Float)v.x = Sin(theta) * rv.y = Cos(theta) * rEnd Function'Summary: Returns the distance between two pointsFunction Dist:Float(x:Float, y:Float, x2:Float, y2:Float)Return Sqrt( (x2 - x) * (x2 - x) + (y2 - y) * (y2 - y))End FunctionEnd Class'Summary: 1d tween valueClass pValField first:FloatField current:FloatField last:FloatField curve:nsEasingCurveField in:Bool = True, out:Bool = TrueMethod New(curve:nsEasingCurve)Self.curve = curveEnd Method'Summary: Sets initial values.Method Set:Void(f:Float, l:Float, in:Bool = True, out:Bool = True)first = fcurrent = flast = lSelf.in = in; Self.out = outEnd MethodMethod Update:Void(percent:Float)current = first + ( (last - first) * curve.Ease(percent, in, out))End MethodEnd Class'Summary: 2d tween valueClass pVal2Field first:= New pVec()Field current:= New pVec()Field last:= New pVec()Field curve:nsEasingCurveField in:Bool = True, out:Bool = TrueMethod New(curve:nsEasingCurve)Self.curve = curveEnd Method'Summary: Sets initial values.Method Set:Void(x:Float, y:Float, x2:Float, y2:Float, in:Bool = True, out:Bool = True)first.x = x; first.y = ycurrent.x = x; current.y = ylast.x = x2; last.y = y2Self.in = in; Self.out = outEnd MethodMethod Update:Void(percent:Float)current.x = first.x + ( (last.x - first.x) * curve.Ease(percent, in, out))current.y = first.y + ( (last.y - first.y) * curve.Ease(percent, in, out))End MethodEnd ClassYou can see an example of it working here. The example extends Particle with a subclass that puts it on a rail, using Particle.RotMulDest() to spin back towards its destination. Since the system was made specifically for this game, it’s a lot less elegant (especially to extend or if you need frame timing) but also a lot more compact and “to the point”. The allocator here is also sloppy and just kills particles regardless of how close they are to expiring, which argyne would’ve done instead as a property of the Particle interface (which would’ve fetched a resource based on time-to-live, iirc).
I would like to release something eventually for monkey/mx2 that is a “best of both worlds” of both of these systems. Perhaps if mx2-only, have particles accept functions which define their behavior and reduce the number of classes/interfaces…
March 30, 2017 at 8:02 am #7660Great stuff.
accept functions which define their behavior
Yes!, like you do above with Method New(curve:nsEasingCurve)
In MX2, we should probably be using Structs as mush as possible for the base objects, but if they are recycled, maybe it wont matter.
One difference from what I’m messing with, is that I read Millisecs in my main loop, and pass that to the animation system. That way I can stop and scale time, which I think is super important. Also, that way I’m sure that all objects have the same time. It does mean a lot of passing nowtime around, and I guess you could put it in a global, but passing it, means you can scale/stop time for some objects/systems, and not for others.
[EDIT] Thinking about this, maybe TimeFunc() should just be replacable, where the default just gets TimeManager.DefaultTime (that in turn returns a mainloop updated Millisecs() ) That would mean no passing of nowtime + a simple way to pause/scale the system, with out sacrificing special timescales if needed
March 31, 2017 at 11:12 am #7668[EDIT] Thinking about this, maybe TimeFunc() should just be replacable, where the default just gets TimeManager.DefaultTime (that in turn returns a mainloop updated Millisecs() ) That would mean no passing of nowtime + a simple way to pause/scale the system, with out sacrificing special timescales if needed
Yes! One upgrade I was thinking of making in my monkey 1 framework was to give every system which relied on time a field for a timeIndex reference. One of the reasons I never got around to it was that it meant wrapping Millisecs() globally (to more easily support pausing) and probably keeping a seperate frame timer too, and as you said probably even going a step further if I needed support for time stretching/compression in my games. In mx2 this can be a simple Func type and ppl who don’t need that complexity can just keep the default timeIndex of a pointer to Millisecs().
 - 
		AuthorPosts
 
You must be logged in to reply to this topic.

