Added time delay to the main update loop (to match HTML5 mojo)

' ***** Start mojo.bmax.bmx ******

Global app:gxtkApp

Type gxtkApp
	Field ginput:gxtkInput
	Field gaudio:gxtkAudio
	Field ggraphics:gxtkGraphics

	Field dead:Int=0
	Field suspended:Int=0
	Field vloading:Int=0
	Field maxloading:Int=0
	Field updateRate:Int=0
	Field nextUpdate:Float=0
	Field updatePeriod:Float=0
	Field startMillis:Float=0

	Method New()' gxtkApp()
		app=Self
		ggraphics=New gxtkGraphics
	EndMethod

	Method Setup()
		ginput=New gxtkInput
		gaudio=New gxtkAudio

		bb_input__1Set_1Input_1Device(ginput)
		bb_audio__1Set_1Audio_1Device(gaudio)

		startMillis=BlitzMaxMillisecs()

		SetFrameRate( 0 )

		InvokeOnCreate()
		InvokeOnRender()
		Update()
	EndMethod

	Method Update()
		While Not AppTerminate()
			If Not updatePeriod return
			Local updates:Int = 0

			Repeat
				nextUpdate:+updatePeriod
				InvokeOnUpdate()
				If Not updatePeriod Then Exit
				If nextUpdate>BlitzMaxMillisecs() Then Exit
				updates:+1
				If updates = 7 Then
					nextUpdate = BlitzMaxMillisecs()
					Exit
				EndIf
			Forever
			InvokeOnRender()
			Local del:Int = nextUpdate - BlitzMaxMillisecs()
			If del < 1 Then del = 1
			Delay(del)
		Wend
	EndMethod

	Method InvokeOnCreate()
		If dead Return
		dead = 1
		OnCreate()
		dead = 0
	EndMethod

	Method InvokeOnUpdate()
		If dead Or suspended Or Not updateRate Or vloading Return
		OnUpdate()
	EndMethod

	Method InvokeOnRender()
		If dead Or suspended Return
		ggraphics.BeginRender()
		If vloading
			OnLoading()
		Else
			OnRender()
		EndIf
		ggraphics.EndRender()
	EndMethod

	Method SetFrameRate( fps:Int )
		If fps
			updatePeriod=1000.0/fps
			nextUpdate=BlitzMaxMillisecs() +updatePeriod
		Else
			updatePeriod=0
		EndIf
	EndMethod

	Method LoadState:String()
'		var file:SharedObject=SharedObject.getLocal( "gxtkapp" );
'		var state:String=file.data.state;
'		file.close();
'		if( state ) return state;
		Return ""
	EndMethod

	Method SaveState:Int( state:String )
'		var file:SharedObject=SharedObject.getLocal( "gxtkapp" );
'		file.data.state=state;
'		file.close();
		Return 0
	EndMethod

	Method LoadString:String( path:String )
		path = "data/" + path
		Return LoadText( path )
	EndMethod

	' ***** GXTK API *****

	Method GraphicsDevice:gxtkGraphics()
		Return ggraphics
	EndMethod

	Method InputDevice:gxtkInput()
		Return ginput
	EndMethod

	Method AudioDevice:gxtkAudio()
		Return gaudio
	EndMethod

	Method SetUpdateRate:Int(hertz:Int)
		updateRate = hertz
		If Not vloading Then SetFrameRate(updateRate)
		Return 0
	EndMethod

	Method MilliSecs:Int()
		Return BlitzMaxMillisecs() - startMillis
	EndMethod

	Method Loading:Int()
		Return vloading
	EndMethod

	Method OnCreate:Int()
		Return 0
	EndMethod

	Method OnUpdate:Int()
		Return 0
	EndMethod

	Method OnSuspend:Int()
		Return 0
	EndMethod

	Method OnResume:Int()
		Return 0
	EndMethod

	Method OnRender:Int()
		Return 0
	EndMethod

	Method OnLoading:Int()
		Return 0
	EndMethod
EndType

Type gxtkGraphics
	Field gmode:Int = 1
	Field ix:Float=1,iy:Float,jx:Float,jy:Float=1,tx:Float,ty:Float
	Field sx:Float=1,sy:Float=1,rot:Float=0

	Method Mode:Int()
		Return gmode
	EndMethod

	Method Cls:Int(r:Int = 0, g:Int = 0, b:Int = 0)
		BlitzMaxCls(r, g, b)
		Return 0
	EndMethod

	Method DrawPoint:Int(x:Float, y:Float)
		Local nx:Float = TransX(x,y)
		Local ny:Float = TransY(x,y)
		Plot nx, ny
	EndMethod

	Method LoadSurface:gxtkSurface(path:String)
		Local image:TImage = LoadImage("data/"+path)
		If image Then
			Local gs:gxtkSurface = New gxtkSurface
			gs.setImage(image)
			Return gs
		EndIf
		Return Null
	EndMethod

	Method BeginRender()
	EndMethod

	Method EndRender()
		Flip
	EndMethod

	Method SetColor:Int(r:Int, g:Int, b:Int)
		BlitzMaxSetColor(r, g, b)
		Return 0
	EndMethod

	Method SetAlpha:Int(a:Float)
		BlitzMaxSetAlpha(a)
		Return 0
	EndMethod

	Method SetBlend:Int(blend:Int)
		BlitzMaxSetBlend(blend)
		Return 0
	EndMethod

	Method Width:Int()
		Return GraphicsWidth()
	EndMethod

	Method Height:Int()
		Return GraphicsHeight()
	EndMethod

	Method SetScissor:Int(x:Int, y:Int, w:Int, h:Int)
		SetViewport(x, y, w, h) ' NOT TESTED!
		Return 0
	EndMethod

	Method TransX:Float(x:Float, y:Float)
		Return ix*x + jx*y + tx
	EndMethod

	Method TransY:Float(x:Float, y:Float)
		Return iy*x + jy*y + ty
	EndMethod

	Method SetMatrix:Int(ix:Float,iy:Float,jx:Float,jy:Float,tx:Float,ty:Float)
		Self.ix = ix ; Self.iy = iy
		Self.jx = jx ; Self.jy = jy
		Self.tx = tx ; Self.ty = ty
		sx = Sqr( (ix*ix) + (jx*jx) )
		sy = Sqr( (iy*iy) + (jy*jy) )
		rot = -Atan2( jx, ix )
		SetTransform( rot, sx, sy )
		Return 0
	EndMethod

	Method DrawSurface:Int(surface:gxtkSurface,x:Float,y:Float)
		Local nx:Float = TransX(x,y)
		Local ny:Float = TransY(x,y)
		DrawImage(surface.image, nx, ny, 0)
		Return 0
	EndMethod

	Method DrawSurface2:Int(surface:gxtkSurface,x:Float,y:Float, srcx:Int, srcy:Int, srcw:Int, srch:Int )
		Local nx:Float = TransX(x,y)
		Local ny:Float = TransY(x,y)
		DrawSubImageRect(surface.image, nx, ny, srcw, srch, srcx, srcy, srcw, srch)
		Return 0
	EndMethod

	Method DrawLine:Int( x1:Float,y1:Float,x2:Float,y2:Float )
		Local nx1:Float = TransX(x1,y1)
		Local ny1:Float = TransY(x1,y1)
		Local nx2:Float = TransX(x2,y2)
		Local ny2:Float = TransY(x2,y2)
		' Need to reset transform so that BlitzMax doesn't try to apply rotation
		SetTransform( 0, 1, 1 )
		BlitzMaxDrawLine(nx1, ny1, nx2, ny2)
		SetTransform( rot, sx, sy )
		Return 0
	EndMethod

	Method DrawOval:Int( x:Float, y:Float, w:Float, h:Float )
		Local nx:Float = TransX(x,y)
		Local ny:Float = TransY(x,y)
		BlitzMaxDrawOval(nx, ny, w, h)
		Return 0
	EndMethod

	Method DrawPoly:Int(vertices:Float[])
		' setting the origin to use the current rotation translation
		SetOrigin(TransX(0,0), TransY(0,0))
		BlitzMaxDrawPoly( vertices )
		SetOrigin(0, 0)
		Return 0
	EndMethod

	Method DrawRect:Int( x:Float, y:Float, w:Float, h:Float )
		Local nx:Float = TransX(x,y)
		Local ny:Float = TransY(x,y)
		BlitzMaxDrawRect(nx, ny, w, h)
		Return 0
	EndMethod
EndType

Type gxtkInput
	Method MouseX:Float()
		Return BRL.PolledInput.MouseX()
	EndMethod

	Method MouseY:Float()
		Return BRL.PolledInput.MouseY()
	EndMethod

	Method KeyDown:Int( key:Int )
		if( key = 384 ) key = 1 ' change TOUCH0 to MOUSE_LMB

		If( key >= 1 And key <= 3 )
			Return BRL.PolledInput.MouseDown( key )
		EndIf
		If key < 256
			Return BRL.PolledInput.KeyDown( key )
		Else
			Return 0
		EndIf
	EndMethod

	Method KeyHit:Int( key:Int )
		if( key = 384 ) key = 1 ' change TOUCH0 to MOUSE_LMB
		If( key >= 1 And key <= 3 )
			Return BRL.PolledInput.MouseHit( key )
		EndIf
		If key < 256
			Return BRL.PolledInput.KeyHit( key )
		Else
			Return 0
		EndIf
	EndMethod

	Method GetChar:Int()
		Return BRL.PolledInput.GetChar()
	EndMethod

	Method JoyX:Int( index:Int )
		Return Pub.FreeJoy.JoyX( index )
	EndMethod

	Method JoyY:Int( index:Int )
		Return Pub.FreeJoy.JoyY( index )
	EndMethod

	Method JoyZ:Int( index:Int )
		Return Pub.FreeJoy.JoyZ( index )
	EndMethod

	Method TouchX:Float( index:Int )
		Return BRL.PolledInput.MouseX()
	EndMethod

	Method TouchY:Float( index:Int )
		Return BRL.PolledInput.MouseY()
	EndMethod

	Method AccelX:Int()
		return 0
	EndMethod

	Method AccelY:Int()
		return 0
	EndMethod

	Method AccelZ:Int()
		return 0
	EndMethod

	Method SetKeyboardEnabled:Int( enabled:int )
		Return 0
	EndMethod
EndType

Type gxtkChannel
	Field channel:TChannel	' null then not playing
	Field sample:gxtkSample
	Field loops:int
	'Field transform:SoundTransform=new SoundTransform()
	'Field pausepos:Number
	Field state:int

	Method New()
		channel = New TChannel
	EndMethod
EndType

Type gxtkAudio
	Field amusicState:Int = 0
	Field channels:gxtkChannel[] = New gxtkChannel[33]

	Method New()
		For local i:Int = 0 To 33 - 1
			channels[i] = New gxtkChannel
		Next
	EndMethod

	Method MusicState:Int()
'		If( musicState = 1 And music.isPlaying() = False )
'			musicState = 0;
'		Endif
		Return amusicState
	EndMethod

	Method PlayMusic:Int( path:String, flags:Int )
		Return 0
	EndMethod

	Method ChannelState:int( channel:int )
		Return -1
	EndMethod

	Method StopMusic()
	EndMethod

	Method StopChannel( channel:Int )
	EndMethod

	Method PlaySample( sound:gxtkSample, channel:Int, flags:Int )
		Local chan:gxtkChannel = channels[channel]

		'If chan.state <> 0 Then chan.channel.Stop() <-- this crashes after the first play!?

		chan.sample = sound
		'chan.loops = flags ? 0x7fffffff : 0;
		'chan.channel = sample.sound.play( 0,chan.loops,chan.transform );
		'chan.channel = sound
		chan.state=1

		PlaySound( sound.sound, chan.channel )
	EndMethod

	Method SetPan( channel:Int, pan:Float )
		Local chan:gxtkChannel = channels[channel]
		chan.channel.SetPan(pan)
	EndMethod

	Method SetRate( channel:Int, rate:Float )
		Local chan:gxtkChannel = channels[channel]
		chan.channel.SetRate(rate)
	EndMethod

	Method PauseMusic:Int()
		Return 0
	EndMethod

	Method ResumeMusic:Int()
		Return 0
	EndMethod

	Method SetMusicVolume:Int( volume:Float )
		Return 0
	EndMethod

	Method SetVolume:Int( channel:Int, volume:Float )
		Local chan:gxtkChannel = channels[channel]
		chan.channel.SetVolume(volume)
		Return 0
	EndMethod

	Method LoadSample:gxtkSample( path:String )
		Local extension:String = ExtractExt( path)
		If extension = "ogg" Or extension = "wav" Then
			Local sound:TSound = LoadSound("data/"+path)
			If sound Then
				Local gs:gxtkSample = New gxtkSample
				gs.setSound(sound)
				Return gs
			EndIf
		Else
			RuntimeError "BlitzMax can only use ogg and wav file formats"
		EndIf
		Return Null
	EndMethod
EndType

Type gxtkSample
	Field sound:TSound

	Method setSound(sound:TSound)
		Self.sound = sound
	EndMethod

	Method Discard()
		Self.sound = null
	EndMethod
EndType

Function BlitzMaxDrawPoly:Int(vertices:Float[])
	DrawPoly vertices
	Return 0
EndFunction

Function BlitzMaxDrawRect:Int( x:Float, y:Float, w:Float, h:Float )
	DrawRect x, y, w, h
	Return 0
EndFunction

Function BlitzMaxDrawOval:Int( x:Float, y:Float, w:Float, h:Float )
	DrawOval x, y, w, h
	Return 0
EndFunction

Function BlitzMaxDrawLine:Int( x1:Float,y1:Float,x2:Float,y2:Float )
	DrawLine x1, y1, x2, y2
	Return 0
EndFunction

Function BlitzMaxMillisecs:Int()
	Return MilliSecs()
EndFunction

Function BlitzMaxSetColor(r:Int, g:Int, b:Int)
	SetColor(r, g, b)
EndFunction

Function BlitzMaxSetBlend(blend:Int)
	Select blend
		Case 0
			SetBlend( ALPHABLEND )
		Case 1
			SetBlend( LIGHTBLEND )
	End Select
EndFunction

Function BlitzMaxSetAlpha(a:Float)
	SetAlpha(a)
EndFunction

Function BlitzMaxCls(r:Int = 0, g:Int = 0, b:Int = 0)
	SetClsColor(r, g, b)
	Cls
EndFunction

Type gxtkSurface
	Field w:Int, h:Int
	Field image:TImage

	Method setImage(image:TImage)
		Self.image = image
	EndMethod

	Method Width:Int()
		Return image.Width
	EndMethod

	Method Height:Int()
		Return image.Height
	EndMethod

	Method Discard()
	EndMethod
EndType

' ***** End mojo.bmax.bmx ******

' ***** Start mojo.bmax.bmx ******

Global app:gxtkApp

Type gxtkApp
	Field ginput:gxtkInput
	Field gaudio:gxtkAudio
	Field ggraphics:gxtkGraphics

	Field dead:Int=0
	Field suspended:Int=0
	Field vloading:Int=0
	Field maxloading:Int=0
	Field updateRate:Int=0
	Field nextUpdate:Float=0
	Field updatePeriod:Float=0
	Field startMillis:Float=0

	Method New()' gxtkApp()
		app=Self
		ggraphics=New gxtkGraphics
	EndMethod

	Method Setup()
		ginput=New gxtkInput
		gaudio=New gxtkAudio

		bb_input__1Set_1Input_1Device(ginput)
		bb_audio__1Set_1Audio_1Device(gaudio)

		startMillis=BlitzMaxMillisecs()

		SetFrameRate( 0 )

		InvokeOnCreate()
		InvokeOnRender()
		Update()
	EndMethod

	Method Update()
		While Not AppTerminate()
			Local updates:Int = 0

			Local cont:Int = 1
			While (cont)
				nextUpdate:+updatePeriod
				InvokeOnUpdate()
				If Not updatePeriod Then cont = 0
				If nextUpdate>BlitzMaxMillisecs() Then cont = 0
				updates:+1
				If updates = 7 Then
					nextUpdate = BlitzMaxMillisecs()
					cont = 0
				EndIf
			Wend
			InvokeOnRender()
		Wend
	EndMethod

	Method InvokeOnCreate()
		If dead Return
		dead = 1
		OnCreate()
		dead = 0
	EndMethod

	Method InvokeOnUpdate()
		If dead Or suspended Or Not updateRate Or vloading Return
		OnUpdate()
	EndMethod

	Method InvokeOnRender()
		If dead Or suspended Return
		ggraphics.BeginRender()
		If vloading
			OnLoading()
		Else
			OnRender()
		EndIf
		ggraphics.EndRender()
	EndMethod

	Method SetFrameRate( fps:Int )
		If fps
			updatePeriod=1000.0/fps
			nextUpdate=BlitzMaxMillisecs() +updatePeriod
		Else
			updatePeriod=0
		EndIf
	EndMethod

	Method LoadState:String()
'		var file:SharedObject=SharedObject.getLocal( "gxtkapp" );
'		var state:String=file.data.state;
'		file.close();
'		if( state ) return state;
		Return ""
	EndMethod

	Method SaveState:Int( state:String )
'		var file:SharedObject=SharedObject.getLocal( "gxtkapp" );
'		file.data.state=state;
'		file.close();
		Return 0
	EndMethod

	Method LoadString:String( path:String )
		path = "data/" + path
		Return LoadText( path )
	EndMethod

	' ***** GXTK API *****

	Method GraphicsDevice:gxtkGraphics()
		Return ggraphics
	EndMethod

	Method InputDevice:gxtkInput()
		Return ginput
	EndMethod

	Method AudioDevice:gxtkAudio()
		Return gaudio
	EndMethod

	Method SetUpdateRate:Int(hertz:Int)
		updateRate = hertz
		If Not vloading Then SetFrameRate(updateRate)
		Return 0
	EndMethod

	Method MilliSecs:Int()
		Return BlitzMaxMillisecs() - startMillis
	EndMethod

	Method Loading:Int()
		Return vloading
	EndMethod

	Method OnCreate:Int()
		Return 0
	EndMethod

	Method OnUpdate:Int()
		Return 0
	EndMethod

	Method OnSuspend:Int()
		Return 0
	EndMethod

	Method OnResume:Int()
		Return 0
	EndMethod

	Method OnRender:Int()
		Return 0
	EndMethod

	Method OnLoading:Int()
		Return 0
	EndMethod
EndType

Type gxtkGraphics
	Field gmode:Int = 1
	Field ix:Float=1,iy:Float,jx:Float,jy:Float=1,tx:Float,ty:Float
	Field sx:Float=1,sy:Float=1,rot:Float=0

	Method Mode:Int()
		Return gmode
	EndMethod

	Method Cls:Int(r:Int = 0, g:Int = 0, b:Int = 0)
		BlitzMaxCls(r, g, b)
		Return 0
	EndMethod

	Method DrawPoint:Int(x:Float, y:Float)
		Local nx:Float = TransX(x,y)
		Local ny:Float = TransY(x,y)
		Plot nx, ny
	EndMethod

	Method LoadSurface:gxtkSurface(path:String)
		Local image:TImage = LoadImage("data/"+path)
		If image Then
			Local gs:gxtkSurface = New gxtkSurface
			gs.setImage(image)
			Return gs
		EndIf
		Return Null
	EndMethod

	Method BeginRender()
	EndMethod

	Method EndRender()
		Flip
	EndMethod

	Method SetColor:Int(r:Int, g:Int, b:Int)
		BlitzMaxSetColor(r, g, b)
		Return 0
	EndMethod

	Method SetAlpha:Int(a:Float)
		BlitzMaxSetAlpha(a)
		Return 0
	EndMethod

	Method SetBlend:Int(blend:Int)
		BlitzMaxSetBlend(blend)
		Return 0
	EndMethod

	Method Width:Int()
		Return GraphicsWidth()
	EndMethod

	Method Height:Int()
		Return GraphicsHeight()
	EndMethod

	Method SetScissor:Int(x:Int, y:Int, w:Int, h:Int)
		SetViewport(x, y, w, h) ' NOT TESTED!
		Return 0
	EndMethod

	Method TransX:Float(x:Float, y:Float)
		Return ix*x + jx*y + tx
	EndMethod

	Method TransY:Float(x:Float, y:Float)
		Return iy*x + jy*y + ty
	EndMethod

	Method SetMatrix:Int(ix:Float,iy:Float,jx:Float,jy:Float,tx:Float,ty:Float)
		Self.ix = ix ; Self.iy = iy
		Self.jx = jx ; Self.jy = jy
		Self.tx = tx ; Self.ty = ty
		sx = Sqr( (ix*ix) + (jx*jx) )
		sy = Sqr( (iy*iy) + (jy*jy) )
		rot = -Atan2( jx, ix )
		SetTransform( rot, sx, sy )
		Return 0
	EndMethod

	Method DrawSurface:Int(surface:gxtkSurface,x:Float,y:Float)
		Local nx:Float = TransX(x,y)
		Local ny:Float = TransY(x,y)
		DrawImage(surface.image, nx, ny, 0)
		Return 0
	EndMethod

	Method DrawSurface2:Int(surface:gxtkSurface,x:Float,y:Float, srcx:Int, srcy:Int, srcw:Int, srch:Int )
		Local nx:Float = TransX(x,y)
		Local ny:Float = TransY(x,y)
		DrawSubImageRect(surface.image, nx, ny, srcw, srch, srcx, srcy, srcw, srch)
		Return 0
	EndMethod

	Method DrawLine:Int( x1:Float,y1:Float,x2:Float,y2:Float )
		Local nx1:Float = TransX(x1,y1)
		Local ny1:Float = TransY(x1,y1)
		Local nx2:Float = TransX(x2,y2)
		Local ny2:Float = TransY(x2,y2)
		' Need to reset transform so that BlitzMax doesn't try to apply rotation
		SetTransform( 0, 1, 1 )
		BlitzMaxDrawLine(nx1, ny1, nx2, ny2)
		SetTransform( rot, sx, sy )
		Return 0
	EndMethod

	Method DrawOval:Int( x:Float, y:Float, w:Float, h:Float )
		Local nx:Float = TransX(x,y)
		Local ny:Float = TransY(x,y)
		BlitzMaxDrawOval(nx, ny, w, h)
		Return 0
	EndMethod

	Method DrawPoly:Int(vertices:Float[])
		' setting the origin to use the current rotation translation
		SetOrigin(TransX(0,0), TransY(0,0))
		BlitzMaxDrawPoly( vertices )
		SetOrigin(0, 0)
		Return 0
	EndMethod

	Method DrawRect:Int( x:Float, y:Float, w:Float, h:Float )
		Local nx:Float = TransX(x,y)
		Local ny:Float = TransY(x,y)
		BlitzMaxDrawRect(nx, ny, w, h)
		Return 0
	EndMethod
EndType

Type gxtkInput
	Method MouseX:Float()
		Return BRL.PolledInput.MouseX()
	EndMethod

	Method MouseY:Float()
		Return BRL.PolledInput.MouseY()
	EndMethod

	Method KeyDown:Int( key:Int )
		if( key = 384 ) key = 1 ' change TOUCH0 to MOUSE_LMB

		If( key >= 1 And key <= 3 )
			Return BRL.PolledInput.MouseDown( key )
		EndIf
		If key < 256
			Return BRL.PolledInput.KeyDown( key )
		Else
			Return 0
		EndIf
	EndMethod

	Method KeyHit:Int( key:Int )
		if( key = 384 ) key = 1 ' change TOUCH0 to MOUSE_LMB
		If( key >= 1 And key <= 3 )
			Return BRL.PolledInput.MouseHit( key )
		EndIf
		If key < 256
			Return BRL.PolledInput.KeyHit( key )
		Else
			Return 0
		EndIf
	EndMethod

	Method GetChar:Int()
		Return BRL.PolledInput.GetChar()
	EndMethod

	Method JoyX:Int( index:Int )
		Return Pub.FreeJoy.JoyX( index )
	EndMethod

	Method JoyY:Int( index:Int )
		Return Pub.FreeJoy.JoyY( index )
	EndMethod

	Method JoyZ:Int( index:Int )
		Return Pub.FreeJoy.JoyZ( index )
	EndMethod

	Method TouchX:Float( index:Int )
		Return BRL.PolledInput.MouseX()
	EndMethod

	Method TouchY:Float( index:Int )
		Return BRL.PolledInput.MouseY()
	EndMethod

	Method AccelX:Int()
		return 0
	EndMethod

	Method AccelY:Int()
		return 0
	EndMethod

	Method AccelZ:Int()
		return 0
	EndMethod

	Method SetKeyboardEnabled:Int( enabled:int )
		Return 0
	EndMethod
EndType

Type gxtkChannel
	Field channel:TChannel	' null then not playing
	Field sample:gxtkSample
	Field loops:int
	'Field transform:SoundTransform=new SoundTransform()
	'Field pausepos:Number
	Field state:int

	Method New()
		channel = New TChannel
	EndMethod
EndType

Type gxtkAudio
	Field amusicState:Int = 0
	Field channels:gxtkChannel[] = New gxtkChannel[33]

	Method New()
		For local i:Int = 0 To 33 - 1
			channels[i] = New gxtkChannel
		Next
	EndMethod

	Method MusicState:Int()
'		If( musicState = 1 And music.isPlaying() = False )
'			musicState = 0;
'		Endif
		Return amusicState
	EndMethod

	Method PlayMusic:Int( path:String, flags:Int )
		Return 0
	EndMethod

	Method ChannelState:int( channel:int )
		Return -1
	EndMethod

	Method StopMusic()
	EndMethod

	Method StopChannel( channel:Int )
	EndMethod

	Method PlaySample( sound:gxtkSample, channel:Int, flags:Int )
		Local chan:gxtkChannel = channels[channel]

		'If chan.state <> 0 Then chan.channel.Stop() <-- this crashes after the first play!?

		chan.sample = sound
		'chan.loops = flags ? 0x7fffffff : 0;
		'chan.channel = sample.sound.play( 0,chan.loops,chan.transform );
		'chan.channel = sound
		chan.state=1

		PlaySound( sound.sound, chan.channel )
	EndMethod

	Method SetPan( channel:Int, pan:Float )
		Local chan:gxtkChannel = channels[channel]
		chan.channel.SetPan(pan)
	EndMethod

	Method SetRate( channel:Int, rate:Float )
		Local chan:gxtkChannel = channels[channel]
		chan.channel.SetRate(rate)
	EndMethod

	Method PauseMusic:Int()
		Return 0
	EndMethod

	Method ResumeMusic:Int()
		Return 0
	EndMethod

	Method SetMusicVolume:Int( volume:Float )
		Return 0
	EndMethod

	Method SetVolume:Int( channel:Int, volume:Float )
		Local chan:gxtkChannel = channels[channel]
		chan.channel.SetVolume(volume)
		Return 0
	EndMethod

	Method LoadSample:gxtkSample( path:String )
		Local extension:String = ExtractExt( path)
		If extension = "ogg" Or extension = "wav" Then
			Local sound:TSound = LoadSound("data/"+path)
			If sound Then
				Local gs:gxtkSample = New gxtkSample
				gs.setSound(sound)
				Return gs
			EndIf
		Else
			RuntimeError "BlitzMax can only use ogg and wav file formats"
		EndIf
		Return Null
	EndMethod
EndType

Type gxtkSample
	Field sound:TSound

	Method setSound(sound:TSound)
		Self.sound = sound
	EndMethod

	Method Discard()
		Self.sound = null
	EndMethod
EndType

Function BlitzMaxDrawPoly:Int(vertices:Float[])
	DrawPoly vertices
	Return 0
EndFunction

Function BlitzMaxDrawRect:Int( x:Float, y:Float, w:Float, h:Float )
	DrawRect x, y, w, h
	Return 0
EndFunction

Function BlitzMaxDrawOval:Int( x:Float, y:Float, w:Float, h:Float )
	DrawOval x, y, w, h
	Return 0
EndFunction

Function BlitzMaxDrawLine:Int( x1:Float,y1:Float,x2:Float,y2:Float )
	DrawLine x1, y1, x2, y2
	Return 0
EndFunction

Function BlitzMaxMillisecs:Int()
	Return MilliSecs()
EndFunction

Function BlitzMaxSetColor(r:Int, g:Int, b:Int)
	SetColor(r, g, b)
EndFunction

Function BlitzMaxSetBlend(blend:Int)
	Select blend
		Case 0
			SetBlend( ALPHABLEND )
		Case 1
			SetBlend( LIGHTBLEND )
	End Select
EndFunction

Function BlitzMaxSetAlpha(a:Float)
	SetAlpha(a)
EndFunction

Function BlitzMaxCls(r:Int = 0, g:Int = 0, b:Int = 0)
	SetClsColor(r, g, b)
	Cls
EndFunction

Type gxtkSurface
	Field w:Int, h:Int
	Field image:TImage

	Method setImage(image:TImage)
		Self.image = image
	EndMethod

	Method Width:Int()
		Return image.Width
	EndMethod

	Method Height:Int()
		Return image.Height
	EndMethod

	Method Discard()
	EndMethod
EndType

' ***** End mojo.bmax.bmx ******


Maximus bug test 2.

Maximus bug test 2.

Strict

Rem
	bbdoc: Blenders Suzanne model loader.
End Rem
Module MaxB3D.MonkeyHeadLoader

Import MaxB3D.Core
Import "data.c"

Extern
	Function get_monkeyhead(verts:Float Ptr Var,normals:Float Ptr Var,tris:Short Ptr Var,vert_count Var,tri_count Var)
End Extern

Type TMeshLoaderMONKEYHEAD Extends TMeshLoader
	Method Run(mesh:TMesh,stream:TStream,url:Object)
		If String(url)<>"*monkeyhead*" Return False
		Global surface:TSurface
		If Not surface
			surface=New TSurface
			Local v:Float Ptr,n:Float Ptr,t:Short Ptr,vc,tc
			get_monkeyhead v,n,t,vc,tc
			surface.Resize vc,tc

			For Local i=0 To vc-1
				surface.SetCoord i,v[i*3+0],v[i*3+1],v[i*3+2]
				'surface.SetNormal i,-n[i*3+0],-n[i*3+1],-n[i*3+2]
			Next

			For Local i=0 To tc-1
				surface.SetTriangle i,t[i*3+0],t[i*3+1],t[i*3+2]
			Next
		EndIf
		mesh.AppendSurface surface.Copy()
		Return True
	End Method

	Method Name$()
		Return "Suzanne"
	End Method
	Method ModuleName$()
		Return "monkeyheadloader"
	End Method
End Type
New TMeshLoaderMONKEYHEAD

Rem
	bbdoc: Needs documentation. #TODO
End Rem
Function CreateMonkeyHead:TMesh(parent:TEntity=Null)
	Return _currentworld.AddMesh("*monkeyhead*",parent)
End Function

Strict

Rem
	bbdoc: Blender's Suzanne model loader.
End Rem
Module MaxB3D.MonkeyHeadLoader

Import MaxB3D.Core
Import "data.c"

Extern
	Function get_monkeyhead(verts:Float Ptr Var,normals:Float Ptr Var,tris:Short Ptr Var,vert_count Var,tri_count Var)
End Extern

Type TMeshLoaderMONKEYHEAD Extends TMeshLoader
	Method Run(mesh:TMesh,stream:TStream,url:Object)
		If String(url)<>"*monkeyhead*" Return False
		Global surface:TSurface
		If Not surface
			surface=New TSurface
			Local v:Float Ptr,n:Float Ptr,t:Short Ptr,vc,tc
			get_monkeyhead v,n,t,vc,tc
			surface.Resize vc,tc

			For Local i=0 To vc-1
				surface.SetCoord i,v[i*3+0],v[i*3+1],v[i*3+2]
				'surface.SetNormal i,-n[i*3+0],-n[i*3+1],-n[i*3+2]
			Next

			For Local i=0 To tc-1
				surface.SetTriangle i,t[i*3+0],t[i*3+1],t[i*3+2]
			Next
		EndIf
		mesh.AppendSurface surface.Copy()
		Return True
	End Method

	Method Name$()
		Return "Suzanne"
	End Method
	Method ModuleName$()
		Return "monkeyheadloader"
	End Method
End Type
New TMeshLoaderMONKEYHEAD

Rem
	bbdoc: Needs documentation. #TODO
End Rem
Function CreateMonkeyHead:TMesh(parent:TEntity=Null)
	Return _currentworld.AddMesh("*monkeyhead*",parent)
End Function


Added version comparison and source archive fetching.

Rem
Copyright (c) 2010 Tim Howard

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
End Rem

Rem
	bbdoc: Maximus module base.
	about: An abstract type for the similarities between module scopes and modules.
End Rem
Type mxModuleBase Abstract

	Field m_name:String, m_description:String

'#region Field accessors

	Rem
		bbdoc: Set the base's name.
		returns: Nothing.
	End Rem
	Method SetName(name:String)
		Assert name, "(mxModuleBase.SetName) name cannot be Null!"
		m_name = name
	End Method

	Rem
		bbdoc: Get the base's name.
		returns: The base's name.
	End Rem
	Method GetName:String()
		Return m_name
	End Method

	Rem
		bbdoc: Set the base's description.
		returns: Nothing.
	End Rem
	Method SetDescription(description:String)
		Assert description, "(mxModuleBase.SetDescription) description cannot be Null!"
		m_description = description
	End Method

	Rem
		bbdoc: Get the base's description.
		returns: The base's description.
	End Rem
	Method GetDescription:String()
		Return m_description
	End Method

'#end region Field accessors

	Rem
		bbdoc: Set a common field from the given variable.
		returns: True if the given variable was handled, or False if it was not.
	End Rem
	Method SetCommonFromVariable:Int(variable:dVariable)
		Select variable.GetName().ToLower()
			Case "desc"
				SetDescription(variable.ValueAsString())
				Return True
		End Select
		Return False
	End Method

End Type

Rem
	bbdoc: Maximus module scope.
End Rem
Type mxModuleScope Extends mxModuleBase

	Field m_modules:dObjectMap

	Method New()
		m_modules = New dObjectMap
	End Method

	Rem
		bbdoc: Add the given module to the scope.
		returns: True if the module was added, or False if it was not (the module is Null).
	End Rem
	Method AddModule:Int(modul:mxModule)
		If modul <> Null
			m_modules._Insert(modul.GetName(), modul)
			modul.SetParent(Self)
			Return True
		End If
		Return False
	End Method

	Rem
		bbdoc: Check if the given module name is found in the scope.
		returns: True if the given module was found in the scope, or False if it was not.
	End Rem
	Method HasModule:Int(modname:String)
		Return m_modules._Contains(modname)
	End Method

	Rem
		bbdoc: Get a module with the given name.
		returns: The module with the given name, or Null if there is no module with the given name.
	End Rem
	Method GetModuleWithName:mxModule(modname:String)
		Return mxModule(m_modules._ValueByKey(modname))
	End Method

	Rem
		bbdoc: Load the given dJObject into the scope.
		returns: Itself, or Null if @root is Null.
	End Rem
	Method FromJSON:mxModuleScope(root:dJObject)
		If root <> Null
			SetName(root.GetName())
			For Local variable:dVariable = EachIn root.GetValues()
				If dJObject(variable)
					AddModule(New mxModule.FromJSON(dJObject(variable)))
				Else
					SetCommonFromVariable(variable)
				End If
			Next
			Return Self
		End If
		Return Null
	End Method

	Rem
		bbdoc: Get the module enumerator for the scope.
		returns: The module enumerator for the scope.
	End Rem
	Method ModuleEnumerator:TMapEnumerator()
		Return m_modules.ValueEnumerator()
	End Method

End Type

Rem
	bbdoc: Maximus module.
End Rem
Type mxModule Extends mxModuleBase

	Field m_parent:mxModuleScope
	Field m_versions:dObjectMap

	Method New()
		m_versions = New dObjectMap
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the module's parent.
		returns: Nothing.
	End Rem
	Method SetParent(parent:mxModuleScope)
		m_parent = parent
	End Method

	Rem
		bbdoc: Get the module's parent.
		returns: The module's parent.
	End Rem
	Method GetParent:mxModuleScope()
		Return m_parent
	End Method

'#end region Field accessors

	Rem
		bbdoc: Add the given version to the module.
		returns: True if the version was added, or False if it was not (the version is Null).
	End Rem
	Method AddVersion:Int(version:mxModuleVersion)
		If version <> Null
			m_versions._Insert(version.GetName(), version)
			version.SetParent(Self)
			Return True
		End If
		Return False
	End Method

	Rem
		bbdoc: Check if there is a version with the given name.
		returns: True if the version was found, or False if it was not.
	End Rem
	Method HasVersion:Int(name:String)
		Return m_versions._Contains(name)
	End Method

	Rem
		bbdoc: Get a version with the given name.
		returns: The version with the given name, or Null if there is no version with the given name.
	End Rem
	Method GetVersionWithName:mxModuleVersion(name:String)
		Return mxModuleVersion(m_versions._ValueByKey(name))
	End Method

	Rem
		bbdoc: Get the latest (non-dev) version for this module.
		returns: The latest version for this module.
	End Rem
	Method GetLatestVersion:mxModuleVersion()
		Local hver:mxModuleVersion
		For Local ver:mxModuleVersion = EachIn VersionEnumerator()
			If ver.GetName() <> "dev"
				If hver = Null Or hver.Compare(ver) = 1
					hver = ver
				End If
			End If
		Next
		Return hver
	End Method

	Rem
		bbdoc: Set a common field from the given variable.
		returns: True if the given variable was handled, or False if it was not.
	End Rem
	Method SetCommonFromVariable:Int(variable:dVariable)
		If Super.SetCommonFromVariable(variable) = True
			Return True
		Else
			Select variable.GetName().ToLower()
				Case "versions"
					For Local jobj:dJObject = EachIn dJObject(variable).GetValues()
						AddVersion(New mxModuleVersion.FromJSON(jobj))
					Next
			End Select
			Return False
		End If
	End Method

	Rem
		bbdoc: Load the given dJObject into the module.
		returns: Itself, or Null if @root is Null.
	End Rem
	Method FromJSON:mxModule(root:dJObject)
		If root <> Null
			SetName(root.GetName())
			For Local variable:dVariable = EachIn root.GetValues()
				SetCommonFromVariable(variable)
			Next
			Return Self
		End If
		Return Null
	End Method

	Rem
		bbdoc: Get the version enumerator for the module.
		returns: The version enumerator for the module.
	End Rem
	Method VersionEnumerator:TMapEnumerator()
		Return m_versions.ValueEnumerator()
	End Method

End Type

Rem
	bbdoc: Maximus module version.
End Rem
Type mxModuleVersion

	Field m_parent:mxModule
	Field m_name:String, m_url:String
	Field m_dependencies:mxModuleDependencies

	Method New()
		m_dependencies = New mxModuleDependencies
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the version's parent.
		returns: Nothing.
	End Rem
	Method SetParent(parent:mxModule)
		m_parent = parent
	End Method

	Rem
		bbdoc: Get the version's parent.
		returns: The version's parent.
	End Rem
	Method GetParent:mxModule()
		Return m_parent
	End Method

	Rem
		bbdoc: Set the version's name.
		returns: Nothing.
	End Rem
	Method SetName(name:String)
		Assert name, "(mxModuleVersion.SetName) name cannot be Null!"
		m_name = name
	End Method

	Rem
		bbdoc: Get the version's name.
		returns: The version's name.
	End Rem
	Method GetName:String()
		Return m_name
	End Method

	Rem
		bbdoc: Set the version's url.
		returns: Nothing.
	End Rem
	Method SetUrl(url:String)
		Assert url, "(mxModuleVersion.SetUrl) url cannot be Null!"
		m_url = url
	End Method

	Rem
		bbdoc: Get the version's url.
		returns: The version's url.
	End Rem
	Method GetUrl:String()
		Return m_url
	End Method

	Rem
		bbdoc: Get the temporary fetch path for the version's source archive.
		returns: The temporary file path for the version's source archive.
	End Rem
	Method GetTemporaryFilePath:String()
		Return "tmp/" + StripDir(GetUrl())
	End Method

	Rem
		bbdoc: Set the version's dependencies.
		returns: Nothing.
	End Rem
	Method SetDependencies(dependencies:mxModuleDependencies)
		m_dependencies = dependencies
	End Method

	Rem
		bbdoc: Get the version's dependencies.
		returns: The version's dependencies.
	End Rem
	Method GetDependencies:mxModuleDependencies()
		Return m_dependencies
	End Method

	Rem
		bbdoc: Get the version parts for the version.
		returns: True if the version is development, in which case the parameters are both 0; or False, in which case the parameters are set accordingly.
	End Rem
	Method GetVersionParts:Int(vmajor:String Var, vminor:String Var)
		If m_name <> "dev"
			Local i:Int = m_name.Find(".")
			If i > -1
				vmajor = m_name[..i]
				vminor = m_name[i + 1..]
			Else
				vmajor = m_name ' I'm not sure what other version formats would be used, so I'm just playing a random card here
			End If
			Return False
		End If
		Return True
	End Method

'#end region Field accessors

	Rem
		bbdoc: Set a common field from the given variable.
		returns: True if the given variable was handled, or False if it was not.
	End Rem
	Method SetCommonFromVariable:Int(variable:dVariable)
		Select variable.GetName().ToLower()
			Case "url"
				SetUrl(variable.ValueAsString())
			Case "deps"
				m_dependencies.FromJSON(dJArray(variable))
				Return True
		End Select
		Return False
	End Method

	Rem
		bbdoc: Load the given dJObject into the version.
		returns: Itself, or Null if @root is Null.
	End Rem
	Method FromJSON:mxModuleVersion(root:dJObject)
		If root <> Null
			SetName(root.GetName())
			For Local variable:dVariable = EachIn root.GetValues()
				SetCommonFromVariable(variable)
			Next
			Return Self
		End If
		Return Null
	End Method

	Rem
		bbdoc: Get the dependency enumerator for the version.
		returns: The dependency enumerator for the version.
	End Rem
	Method DependencyEnumerator:TMapEnumerator()
		Return m_dependencies.DependencyEnumerator()
	End Method

	Rem
		bbdoc: Compare the version with the given version.
		returns: 0 if the two versions are the same (same in version number alone), 1 if this version is greater than the given, or -1 if the given version is greater than this version.
	End Rem
	Method Compare:Int(with:Object)
		Local ver:mxModuleVersion = mxModuleVersion(with)
		If ver <> Null
			If m_name = ver.m_name
				Return 0
			Else
				Local smajor:String, sminor:String, wmajor:String, wminor:String
				Local sdev:Int = GetVersionParts(smajor, sminor), wdev:Int = ver.GetVersionParts(wmajor, wminor)
				If (sdev = True And wdev = True) Or (smajor = wmajor And sminor = wminor)
					Return 0
				Else If smajor > wmajor
					Return 1
				Else If smajor = wmajor
					If sminor > wminor
						Return 1
					Else
						Return -1
					End If
				Else
					Return -1
				End If
			End If
		End If
		Return 0
	End Method

	Rem
		bbdoc: Fetch the version's source archive.
		returns: Nothing.
	End Rem
	Method FetchSourceArchive()
		Local file:String = GetTemporaryFilePath()
		logger.LogMessage("fetching: " + GetUrl() + " -> " + file + "~t", False)
		If FileType(file) = FILETYPE_NONE
			Local stream:TStream = WriteFileExplicitly(file)
			If stream <> Null
				Local request:TRESTRequest = New TRESTRequest, response:TRESTResponse
				request.SetProgressCallback(_ProgressCallback, New _mxProgressStore)
				request.SetStream(stream)
				Try
					response = request.Call(GetUrl(), Null, "GET")
				Catch e:Object
					stream.Close()
					DeleteFile(file)
					logger.LogMessage("")
					ThrowError(_s("error.fetch.error", [e.ToString()]))
				End Try
				stream.Close()
				If response.responseCode = 200
					logger.LogMessage(_s("message.fetch.done", [String(response.responseCode)]))
				Else
					DeleteFile(file)
					logger.LogMessage("")
					ThrowError(_s("error.fetch.error", ["Bad response code: " + String(response.responseCode)]))
				End If
			Else
				ThrowError(_s("error.writeperms", [file]))
			End If
		Else
			logger.LogMessage(_s("message.fetch.alreadyfetched"))
		End If
	End Method

	Rem
		bbdoc: Progress callback for source archive fetching.
		returns: Zero (no error).
	End Rem
	Function _ProgressCallback:Int(data:Object, dltotal:Double, dlnow:Double, ultotal:Double, ulnow:Double)
		Local store:_mxProgressStore = _mxProgressStore(data)
		Local prog:Int = (dlnow / dltotal) * 100
		If prog > store.m_progress + 5
			store.m_progress = prog
			WriteStdOut(".")
		End If
		Return 0
	End Function

End Type

Rem
	bbdoc: Maximus temporary progress storage for archive fetching.
End Rem
Type _mxProgressStore

	Field m_progress:Int = 0

End Type

Rem
Copyright (c) 2010 Tim Howard

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
End Rem

Rem
	bbdoc: Maximus module base.
	about: An abstract type for the similarities between module scopes and modules.
End Rem
Type mxModuleBase Abstract

	Field m_name:String, m_description:String

'#region Field accessors

	Rem
		bbdoc: Set the base's name.
		returns: Nothing.
	End Rem
	Method SetName(name:String)
		Assert name, "(mxModuleBase.SetName) name cannot be Null!"
		m_name = name
	End Method

	Rem
		bbdoc: Get the base's name.
		returns: The base's name.
	End Rem
	Method GetName:String()
		Return m_name
	End Method

	Rem
		bbdoc: Set the base's description.
		returns: Nothing.
	End Rem
	Method SetDescription(description:String)
		Assert description, "(mxModuleBase.SetDescription) description cannot be Null!"
		m_description = description
	End Method

	Rem
		bbdoc: Get the base's description.
		returns: The base's description.
	End Rem
	Method GetDescription:String()
		Return m_description
	End Method

'#end region Field accessors

	Rem
		bbdoc: Set a common field from the given variable.
		returns: True if the given variable was handled, or False if it was not.
	End Rem
	Method SetCommonFromVariable:Int(variable:dVariable)
		Select variable.GetName().ToLower()
			Case "desc"
				SetDescription(variable.ValueAsString())
				Return True
		End Select
		Return False
	End Method

End Type

Rem
	bbdoc: Maximus module scope.
End Rem
Type mxModuleScope Extends mxModuleBase

	Field m_modules:dObjectMap

	Method New()
		m_modules = New dObjectMap
	End Method

	Rem
		bbdoc: Add the given module to the scope.
		returns: True if the module was added, or False if it was not (the module is Null).
	End Rem
	Method AddModule:Int(modul:mxModule)
		If modul <> Null
			m_modules._Insert(modul.GetName(), modul)
			modul.SetParent(Self)
			Return True
		End If
		Return False
	End Method

	Rem
		bbdoc: Check if the given module name is found in the scope.
		returns: True if the given module was found in the scope, or False if it was not.
	End Rem
	Method HasModule:Int(modname:String)
		Return m_modules._Contains(modname)
	End Method

	Rem
		bbdoc: Get a module with the given name.
		returns: The module with the given name, or Null if there is no module with the given name.
	End Rem
	Method GetModuleWithName:mxModule(modname:String)
		Return mxModule(m_modules._ValueByKey(modname))
	End Method

	Rem
		bbdoc: Load the given dJObject into the scope.
		returns: Itself, or Null if @root is Null.
	End Rem
	Method FromJSON:mxModuleScope(root:dJObject)
		If root <> Null
			SetName(root.GetName())
			For Local variable:dVariable = EachIn root.GetValues()
				If dJObject(variable)
					AddModule(New mxModule.FromJSON(dJObject(variable)))
				Else
					SetCommonFromVariable(variable)
				End If
			Next
			Return Self
		End If
		Return Null
	End Method

	Rem
		bbdoc: Get the module enumerator for the scope.
		returns: The module enumerator for the scope.
	End Rem
	Method ModuleEnumerator:TMapEnumerator()
		Return m_modules.ValueEnumerator()
	End Method

End Type

Rem
	bbdoc: Maximus module.
End Rem
Type mxModule Extends mxModuleBase

	Field m_parent:mxModuleScope
	Field m_versions:dObjectMap

	Method New()
		m_versions = New dObjectMap
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the module's parent.
		returns: Nothing.
	End Rem
	Method SetParent(parent:mxModuleScope)
		m_parent = parent
	End Method

	Rem
		bbdoc: Get the module's parent.
		returns: The module's parent.
	End Rem
	Method GetParent:mxModuleScope()
		Return m_parent
	End Method

'#end region Field accessors

	Rem
		bbdoc: Add the given version to the module.
		returns: True if the version was added, or False if it was not (the version is Null).
	End Rem
	Method AddVersion:Int(version:mxModuleVersion)
		If version <> Null
			m_versions._Insert(version.GetName(), version)
			version.SetParent(Self)
			Return True
		End If
		Return False
	End Method

	Rem
		bbdoc: Check if there is a version with the given name.
		returns: True if the version was found, or False if it was not.
	End Rem
	Method HasVersion:Int(name:String)
		Return m_versions._Contains(name)
	End Method

	Rem
		bbdoc: Get a version with the given name.
		returns: The version with the given name, or Null if there is no version with the given name.
	End Rem
	Method GetVersionWithName:mxModuleVersion(name:String)
		Return mxModuleVersion(m_versions._ValueByKey(name))
	End Method

	Rem
		bbdoc: Set a common field from the given variable.
		returns: True if the given variable was handled, or False if it was not.
	End Rem
	Method SetCommonFromVariable:Int(variable:dVariable)
		If Super.SetCommonFromVariable(variable) = True
			Return True
		Else
			Select variable.GetName().ToLower()
				Case "versions"
					For Local jobj:dJObject = EachIn dJObject(variable).GetValues()
						AddVersion(New mxModuleVersion.FromJSON(jobj))
					Next
			End Select
			Return False
		End If
	End Method

	Rem
		bbdoc: Load the given dJObject into the module.
		returns: Itself, or Null if @root is Null.
	End Rem
	Method FromJSON:mxModule(root:dJObject)
		If root <> Null
			SetName(root.GetName())
			For Local variable:dVariable = EachIn root.GetValues()
				SetCommonFromVariable(variable)
			Next
			Return Self
		End If
		Return Null
	End Method

	Rem
		bbdoc: Get the version enumerator for the module.
		returns: The version enumerator for the module.
	End Rem
	Method VersionEnumerator:TMapEnumerator()
		Return m_versions.ValueEnumerator()
	End Method

End Type

Rem
	bbdoc: Maximus module version.
End Rem
Type mxModuleVersion

	Field m_parent:mxModule
	Field m_name:String, m_url:String
	Field m_dependencies:mxModuleDependencies

	Method New()
		m_dependencies = New mxModuleDependencies
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the version's parent.
		returns: Nothing.
	End Rem
	Method SetParent(parent:mxModule)
		m_parent = parent
	End Method

	Rem
		bbdoc: Get the version's parent.
		returns: The version's parent.
	End Rem
	Method GetParent:mxModule()
		Return m_parent
	End Method

	Rem
		bbdoc: Set the version's name.
		returns: Nothing.
	End Rem
	Method SetName(name:String)
		Assert name, "(mxModuleVersion.SetName) name cannot be Null!"
		m_name = name
	End Method

	Rem
		bbdoc: Get the version's name.
		returns: The version's name.
	End Rem
	Method GetName:String()
		Return m_name
	End Method

	Rem
		bbdoc: Set the version's url.
		returns: Nothing.
	End Rem
	Method SetUrl(url:String)
		Assert url, "(mxModuleVersion.SetUrl) url cannot be Null!"
		m_url = url
	End Method

	Rem
		bbdoc: Get the version's url.
		returns: The version's url.
	End Rem
	Method GetUrl:String()
		Return m_url
	End Method

	Rem
		bbdoc: Set the version's dependencies.
		returns: Nothing.
	End Rem
	Method SetDependencies(dependencies:mxModuleDependencies)
		m_dependencies = dependencies
	End Method

	Rem
		bbdoc: Get the version's dependencies.
		returns: The version's dependencies.
	End Rem
	Method GetDependencies:mxModuleDependencies()
		Return m_dependencies
	End Method

'#end region Field accessors

	Rem
		bbdoc: Set a common field from the given variable.
		returns: True if the given variable was handled, or False if it was not.
	End Rem
	Method SetCommonFromVariable:Int(variable:dVariable)
		Select variable.GetName().ToLower()
			Case "url"
				SetUrl(variable.ValueAsString())
			Case "deps"
				m_dependencies.FromJSON(dJArray(variable))
				Return True
		End Select
		Return False
	End Method

	Rem
		bbdoc: Load the given dJObject into the version.
		returns: Itself, or Null if @root is Null.
	End Rem
	Method FromJSON:mxModuleVersion(root:dJObject)
		If root <> Null
			SetName(root.GetName())
			For Local variable:dVariable = EachIn root.GetValues()
				SetCommonFromVariable(variable)
			Next
			Return Self
		End If
		Return Null
	End Method

End Type


BSP loader now accepts TBSPModels.

BSP loader now accepts TBSPModels.

Strict

Rem
	bbdoc: Loads a TBSPTree into a mesh.
End Rem
Module MaxB3D.BSPLoader
ModuleInfo "Author: Kevin Primm"
ModuleInfo "License: MIT"

Import MaxB3D.Core
Import Prime.BSP

Type TMeshLoaderBSP Extends TMeshLoader
	Method Run(config:TWorldConfig,mesh:TMesh,stream:TStream,url:Object)
		Local tree:TBSPTree=TBSPTree(url)
		If TBSPModel(url) tree = TBSPModel(url)._tree
		If tree=Null Return False

		Local surface:TSurface=mesh.AddSurface()
		AddTreeToSurface tree,surface

		Return True
	End Method

	Function AddTreeToSurface(tree:TBSPTree,surface:TSurface)
		Local node:TBSPNode=tree.Node
		If Not node Return
		For Local poly:TBSPPolygon=EachIn node.On
			Local ptA:TVector=poly.Point[0],v0
			v0=surface.AddVertex(ptA.x,ptA.y,ptA.z)
			For Local i=1 To poly.Count()-2
				Local ptB:TVector=poly.Point[i],ptC:TVector=poly.Point[i+1]
				Local v1=surface.AddVertex(ptB.x,ptB.y,ptB.z)
				Local v2=surface.AddVertex(ptC.x,ptC.y,ptC.z)
				Local t=surface.AddTriangle(v0,v1,v2)
				surface.SetTriangleNormal t,-poly.Plane.x,-poly.Plane.y,-poly.Plane.z
			Next
		Next
		AddTreeToSurface node.In,surface
		AddTreeToSurface node.Out,surface
	End Function

	Method ModuleName$()
		Return "bsploader"
	End Method

	Method Info$()
		Return "BSP Tree|"
	End Method
End Type
New TMeshLoaderBSP

Strict

Rem
	bbdoc: Loads a TBSPTree into a mesh.
End Rem
Module MaxB3D.BSPLoader
ModuleInfo "Author: Kevin Primm"
ModuleInfo "License: MIT"

Import MaxB3D.Core
Import Prime.BSP

Type TMeshLoaderBSP Extends TMeshLoader
	Method Run(config:TWorldConfig,mesh:TMesh,stream:TStream,url:Object)
		Local tree:TBSPTree=TBSPTree(url)
		If tree=Null Return False

		Local surface:TSurface=mesh.AddSurface()
		AddTreeToSurface tree,surface

		Return True
	End Method

	Function AddTreeToSurface(tree:TBSPTree,surface:TSurface)
		Local node:TBSPNode=tree.Node
		If Not node Return
		For Local poly:TBSPPolygon=EachIn node.On
			Local ptA:TVector=poly.Point[0],v0
			v0=surface.AddVertex(ptA.x,ptA.y,ptA.z)
			For Local i=1 To poly.Count()-2
				Local ptB:TVector=poly.Point[i],ptC:TVector=poly.Point[i+1]
				Local v1=surface.AddVertex(ptB.x,ptB.y,ptB.z)
				Local v2=surface.AddVertex(ptC.x,ptC.y,ptC.z)
				Local t=surface.AddTriangle(v0,v1,v2)
				surface.SetTriangleNormal t,-poly.Plane.x,-poly.Plane.y,-poly.Plane.z
			Next
		Next
		AddTreeToSurface node.In,surface
		AddTreeToSurface node.Out,surface
	End Function

	Method ModuleName$()
		Return "bsploader"
	End Method

	Method Info$()
		Return "BSP Tree|"
	End Method
End Type
New TMeshLoaderBSP


Maximus would crash when listing a module that didn't have any versions

Rem
	bbdoc: Maximus 'list' argument implementation.
End Rem
Type mxListImpl Extends dArgumentImplementation

	Field m_queuemap:dObjectMap = New dObjectMap

	Method New()
		init(["list"])
	End Method

	Rem
		bbdoc: Check the current arguments for errors (according to the specific implementation).
		returns: Nothing.
		about: This method will throw an error if the arguments are invalid.
	End Rem
	Method CheckArgs()
	End Method

	Rem
		bbdoc: Get a string describing the typical usage of the argument.
		returns: A string describing the typical usage of the argument.
	End Rem
	Method GetUsage:String()
		Return _s("arg.list.usage")
	End Method

	Rem
		bbdoc: Execute the implementation's operation.
		returns: Nothing.
	End Rem
	Method Execute()
		m_queuemap.Clear()
		Local nfounds:TListEx = New TListEx
		Local sources:mxSourcesHandler = mainapp.m_sourceshandler
		If sources
			If sources.Count() > 0
				If GetArgumentCount() > 0
					For Local variable:dStringVariable = EachIn m_args
						Local scope:mxModuleScope
						Local arg:String = variable.Get()
						If arg.Contains(".")
							scope = sources.GetScopeWithName(mxModUtils.GetScopeFromID(arg))
							If scope
								Local modul:mxModule = scope.GetModuleWithName(mxModUtils.GetNameFromID(arg))
								If modul
									QueueModule(modul)
								Else
									nfounds.AddLast(arg)
								End If
							Else
								nfounds.AddLast(arg)
							End If
						Else
							scope = sources.GetScopeWithName(arg)
							If scope
								QueueScope(scope)
							Else
								nfounds.AddLast(arg)
							End If
						End If
					Next
				Else
					For Local modscope:mxModuleScope = EachIn sources.ValueEnumerator()
						QueueScope(modscope)
					Next
				End If
				ReportQueue()
				If nfounds.Count() > 0
					Local a:String
					logger.LogWarning(_s("arg.list.unfound"))
					For Local b:String = EachIn nfounds
						a:+ b + " "
					Next
					a = a[..a.Length - 1]
					logger.LogMessage("~t" + a)
				End If
			Else
				logger.LogMessage(_s("arg.list.nosources"))
			End If
		Else
			ThrowError(_s("error.list.nosources"))
		End If
	End Method

	Rem
		bbdoc: Queue the given module scope's modules for printing.
		returns: Nothing.
	End Rem
	Method QueueScope(modscope:mxModuleScope)
		If modscope
			For Local modul:mxModule = EachIn modscope.ModuleEnumerator()
				QueueModule(modul)
			Next
		End If
	End Method

	Rem
		bbdoc: Queue the given module for printing.
		returns: Nothing.
	End Rem
	Method QueueModule(modul:mxModule)
		If modul
			m_queuemap._Insert(modul.GetName(), modul)
		End If
	End Method

	Rem
		bbdoc: Report the module queue.
		returns: Nothing.
	End Rem
	Method ReportQueue()
		Local namelen:Int, verlen:Int, modul:mxModule, latest_version:mxModuleVersion
		Local str:String
		For modul = EachIn m_queuemap.ValueEnumerator()
			str = modul.GetParent().GetName() + "." + modul.GetName()
			If str.Length > namelen Then namelen = str.Length
			latest_version:mxModuleVersion = modul.GetLatestVersion()
			If latest_version Then str = latest_version.GetName()
			If str.Length > verlen Then verlen = str.Length
		Next
		For modul = EachIn m_queuemap.ValueEnumerator()
			latest_version:mxModuleVersion = modul.GetLatestVersion()
			If latest_version Then str = latest_version.GetName()
			If str <> "dev" And modul.HasVersion("dev") Then str:+ " (has dev)"
			logger.LogMessage((modul.GetParent().GetName() + "." + modul.GetName())[..namelen] + " - " + str[..verlen] + " - " + modul.GetDescription())
		Next
	End Method

End Type

Rem
	bbdoc: Maximus 'list' argument implementation.
End Rem
Type mxListImpl Extends dArgumentImplementation

	Field m_queuemap:dObjectMap = New dObjectMap

	Method New()
		init(["list"])
	End Method

	Rem
		bbdoc: Check the current arguments for errors (according to the specific implementation).
		returns: Nothing.
		about: This method will throw an error if the arguments are invalid.
	End Rem
	Method CheckArgs()
	End Method

	Rem
		bbdoc: Get a string describing the typical usage of the argument.
		returns: A string describing the typical usage of the argument.
	End Rem
	Method GetUsage:String()
		Return _s("arg.list.usage")
	End Method

	Rem
		bbdoc: Execute the implementation's operation.
		returns: Nothing.
	End Rem
	Method Execute()
		m_queuemap.Clear()
		Local nfounds:TListEx = New TListEx
		Local sources:mxSourcesHandler = mainapp.m_sourceshandler
		If sources
			If sources.Count() > 0
				If GetArgumentCount() > 0
					For Local variable:dStringVariable = EachIn m_args
						Local scope:mxModuleScope
						Local arg:String = variable.Get()
						If arg.Contains(".")
							scope = sources.GetScopeWithName(mxModUtils.GetScopeFromID(arg))
							If scope
								Local modul:mxModule = scope.GetModuleWithName(mxModUtils.GetNameFromID(arg))
								If modul
									QueueModule(modul)
								Else
									nfounds.AddLast(arg)
								End If
							Else
								nfounds.AddLast(arg)
							End If
						Else
							scope = sources.GetScopeWithName(arg)
							If scope
								QueueScope(scope)
							Else
								nfounds.AddLast(arg)
							End If
						End If
					Next
				Else
					For Local modscope:mxModuleScope = EachIn sources.ValueEnumerator()
						QueueScope(modscope)
					Next
				End If
				ReportQueue()
				If nfounds.Count() > 0
					Local a:String
					logger.LogWarning(_s("arg.list.unfound"))
					For Local b:String = EachIn nfounds
						a:+ b + " "
					Next
					a = a[..a.Length - 1]
					logger.LogMessage("~t" + a)
				End If
			Else
				logger.LogMessage(_s("arg.list.nosources"))
			End If
		Else
			ThrowError(_s("error.list.nosources"))
		End If
	End Method

	Rem
		bbdoc: Queue the given module scope's modules for printing.
		returns: Nothing.
	End Rem
	Method QueueScope(modscope:mxModuleScope)
		If modscope
			For Local modul:mxModule = EachIn modscope.ModuleEnumerator()
				QueueModule(modul)
			Next
		End If
	End Method

	Rem
		bbdoc: Queue the given module for printing.
		returns: Nothing.
	End Rem
	Method QueueModule(modul:mxModule)
		If modul
			m_queuemap._Insert(modul.GetName(), modul)
		End If
	End Method

	Rem
		bbdoc: Report the module queue.
		returns: Nothing.
	End Rem
	Method ReportQueue()
		Local namelen:Int, verlen:Int, modul:mxModule
		Local str:String
		For modul = EachIn m_queuemap.ValueEnumerator()
			str = modul.GetParent().GetName() + "." + modul.GetName()
			If str.Length > namelen Then namelen = str.Length
			str = modul.GetLatestVersion().GetName()
			If str <> "dev" And modul.HasVersion("dev") Then str:+ " (has dev)"
			If str.Length > verlen Then verlen = str.Length
		Next
		For modul = EachIn m_queuemap.ValueEnumerator()
			str = modul.GetLatestVersion().GetName()
			If str <> "dev" And modul.HasVersion("dev") Then str:+ " (has dev)"
			logger.LogMessage((modul.GetParent().GetName() + "." + modul.GetName())[..namelen] + " - " + str[..verlen] + " - " + modul.GetDescription())
		Next
	End Method

End Type


Added an auxiliary method to the LuGI core to bind glue functions to types (so you don't have to call p_lugi_register_method yourself).

Rem
	LuGI - Copyright (c) 2009 Noel R. Cower

	Permission is hereby granted, free of charge, to any person obtaining a copy
	of this software and associated documentation files (the "Software"), to deal
	in the Software without restriction, including without limitation the rights
	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
	copies of the Software, and to permit persons to whom the Software is
	furnished to do so, subject to the following conditions:

	The above copyright notice and this permission notice shall be included in
	all copies or substantial portions of the Software.

	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
	THE SOFTWARE.
EndRem

SuperStrict

Module LuGI.Core

ModuleInfo "Name: LuGI Core"
ModuleInfo "Description: Core API provided by LuGI to interact with BMax objects via Lua"
ModuleInfo "Version: 0.8.02"
ModuleInfo "Author: Noel Cower"
ModuleInfo "License: MIT"
ModuleInfo "URL: http://github.com/nilium/lugi.mod/"

?Threaded
Import Brl.Threads
?
Import Brl.Reflection
Import Brl.LinkedList
Import Pub.Lua
Import "lgcore.cpp"

Public

' LuGI field types - used by LuGI to determine how to access fields at runtime
' The only reason these are public is because the generated code uses them, otherwise I'd hide
' them away too.  But then if I hide them and use number constants in the generated code, we'll
' get the chance that changing these later will break existing glue code, and doing that is bad.
Const LUGI_BYTEFIELD:Int = $0001
Const LUGI_SHORTFIELD:Int = $0002
Const LUGI_INTFIELD:Int = $0004
Const LUGI_FLOATFIELD:Int = $0008
Const LUGI_LONGFIELD:Int = $0010
Const LUGI_DOUBLEFIELD:Int = $0020
Const LUGI_STRINGFIELD:Int = $0040
Const LUGI_OBJECTFIELD:Int = $0080
Const LUGI_ARRAYFIELD:Int = $0100
' Optional flag to specify that an integer field (Int, Short, Byte, or Long) should be treated as a
' boolean value when passed to Lua
Const LUGI_BOOLFIELDOPT:Int = $8000

Type LuGIInitFunction

	' Registers the callback provided as a pre-init stage routine
	Method PreInit:LuGIInitFunction(cb(vm:Byte Ptr, rfield(off%, typ%, name$, clas@ Ptr), rmethod(fn:Int(state@ Ptr), name$, clas@ Ptr)), requiresState%=True)
		_cb = cb
		_reqState = requiresState
		?Threaded
		p_lugi_initlock.Lock()
		?
		If p_lugi_preinit_cb = Null Then
			p_lugi_preinit_cb = New TList
		EndIf
		p_lugi_preinit_cb.AddLast(Self)
		?Threaded
		p_lugi_initlock.Unlock()
		?
		Return Self
	End Method

	' Registers the callback provided as a post-init stage routine
	Method PostInit:LuGIInitFunction(cb(state:Byte Ptr, constructor:Int(state:Byte Ptr)))
		_cb = cb
		?Threaded
		p_lugi_initlock.Lock()
		?
		If p_lugi_postinit_cb = Null Then
			p_lugi_postinit_cb = New TList
		EndIf
		p_lugi_postinit_cb.AddLast(Self)
		?Threaded
		p_lugi_initlock.Unlock()
		?
		Return Self
	End Method

	' #region PRIVATE

	Field _cb:Byte Ptr
	Field _reqState:Int

	Method Pre(vm:Byte Ptr, rfield(off%, typ%, name$, clas@ Ptr), rmethod(fn:Int(state@ Ptr), name$, clas@ Ptr))
		Local cb(vm:Byte Ptr, rfield(off%, typ%, name$, clas@ Ptr), rmethod(fn:Int(state@ Ptr), name$, clas@ Ptr)) = _cb
		If Not _reqState Then
		    p_lugi_preinit_cb.Remove(Self)
		    cb(Null, rfield, rmethod) ' No VM is passed, since it's not required
		Else
    		cb(vm, rfield, rmethod)
    	EndIf
	End Method

	Method Post(vm:Byte Ptr, constructor:Int(state:Byte Ptr))
		Local cb(vm:Byte Ptr, constructor:Int(state:Byte Ptr)) = _cb
		cb(vm, constructor)
	End Method

	' #endregion

End Type

Private

' There is really no good way to do this in a thread-safe manner, so the best I can do is to make
' all access to the initialization lists a critical section
?Threaded
Global p_lugi_initlock:TMutex = TMutex.Create()
?
Global p_lugi_preinit_cb:TList
Global p_lugi_postinit_cb:TList

' Executes init functions that are to occur prior to creation of any BMax objects, VMTs, etc.
' Doesn't lock the lists since it's only supposed to be called after the lists have been locked
Function PreInitLuGI(vm@ Ptr)
	If Not p_lugi_preinit_cb Then
		Return
	EndIf
	For Local cb:LuGIInitFunction = EachIn p_lugi_preinit_cb
		cb.Pre(vm, p_lugi_register_field, p_lugi_register_method)
	Next
End Function

' Executes init functions that are to occur following the creation of BMax VMTs, metatables, cache, etc.
' Doesn't lock the lists since it's only supposed to be called after the lists have been locked
Function PostInitLuGI(vm@ Ptr)
	If Not p_lugi_postinit_cb Then
		Return
	EndIf
	For Local cb:LuGIInitFunction = EachIn p_lugi_postinit_cb
		cb.Post(vm, p_lugi_new_object)
	Next
End Function

' Private/internal code that shouldn't be directly accessed (only provided)
Extern "C"
	Function p_lugi_register_method(fn:Int(state@ Ptr), name$, clas@ Ptr=Null)
	Function p_lugi_register_field(off%, typ%, name$, clas@ Ptr)

	Function p_lugi_init(state@ Ptr)

	' Constructor object - push with BBClass for type as upvalue
	Function p_lugi_new_object:Int(state@ Ptr)
End Extern

Public

Extern "C"
	' Determining if an object on the stack is a BMax object
	Function lua_isbmaxobject:Int(state@ Ptr, index:Int)

	' Pushing/getting BBObjects (BBObjects, BBStrings, and BBArrays to tables/tables to BBArrays respectively)
	Function lua_pushbmaxobject(state@ Ptr, obj:Object)
	Function lua_tobmaxobject:Object(state@ Ptr, index:Int)

	' Pushing/getting BBArrays (BBArrays to tables/tables to BBArrays respectively)
	' @arr has to be treated as an Object in BMax since passing an Int[] as an Object[] doesn't work
	Function lua_pushbmaxarray(state@ Ptr, arr:Object)
	Function lua_tobmaxarray:Object(state@ Ptr, index:Int)
End Extern

' Initializes the Lua state for use with BMax objects via LuGI
Function InitLuGI(vm:Byte Ptr)
	?Threaded
	p_lugi_initlock.Lock()
	?
	PreInitLuGI(vm)
	p_lugi_init(vm)
	PostInitLuGI(vm)
	?Threaded
	p_lugi_initlock.Unlock()
	?
End Function

Function BindFunctionToType(fn:Int(L@Ptr), as$, typ:TTypeID)
	p_lugi_register_method(fn, as, Byte Ptr(typ._class))
End Function


Extra test case for Lexer

Extra test case for Lexer

SuperStrict

Module test.mod1
ModuleInfo "Name: test.mod1"
ModuleInfo "Version: 1.1.15"
ModuleInfo "License: MIT"
ModuleInfo "Author: Christiaan Kras"

Framework brl.basic
Import htbaapub.rest
Import brl.retro
'Import pub.zlib
Rem
	Import pub.lua
	Import pub.enet
	Import "test.bmx"
	Include "test2.bmx"
end rem

Include "inc/more_imports.bmx"
Import "inc/other_imports.bmx"
'This include will be ignored by Maximus::Class::Lexer
Include "src/*.h"


Added newline option to logging methods.

Rem
Copyright (c) 2010 Tim Howard

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
End Rem

Rem
	bbdoc: Maximus basic logger.
End Rem
Type mxLogger

	Rem
		bbdoc: Log a message.
		returns: Nothing.
		about: If @newline is True, a new line will be started, if it is False, a new line will not be started.
	End Rem
	Method LogMessage(message:String, newline:Int = True)
		If newline = True
			Print(message)
		Else
			WriteStdOut(message)
		End If
	End Method

	Rem
		bbdoc: Log a warning message.
		returns: Nothing.
		about: If @newline is True, a new line will be started, if it is False, a new line will not be started.
	End Rem
	Method LogWarning(warning:String, newline:Int = True)
		If newline = True
			Print(_s("message.warning", [warning]))
		Else
			WriteStdOut(_s("message.warning", [warning]))
		End If
	End Method

	Rem
		bbdoc: Log an error message.
		returns: Nothing.
		about: If @newline is True, a new line will be started, if it is False, a new line will not be started.
	End Rem
	Method LogError(error:String, newline:Int = True)
		If newline = True
			Print(_s("message.error", [error]))
		Else
			WriteStdOut(_s("message.error", [error]))
		End If
	End Method

End Type

Rem
Copyright (c) 2010 Tim Howard

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
End Rem

Rem
	bbdoc: Maximus basic logger.
End Rem
Type mxLogger

	Rem
		bbdoc: Log a message.
		returns: Nothing.
	End Rem
	Method LogMessage(message:String)
		Print(message)
	End Method

	Rem
		bbdoc: Log a warning message.
		returns: Nothing.
	End Rem
	Method LogWarning(warning:String)
		Print(_s("message.warning", [warning]))
	End Method

	Rem
		bbdoc: Log an error message.
		returns: Nothing.
	End Rem
	Method LogError(error:String)
		Print(_s("message.error", [error]))
	End Method

End Type


mxModuleVersion now uses mxVersionObject for sorting

refs gh-1

Rem
	bbdoc: Maximus module base.
	about: An abstract type for the similarities between module scopes and modules.
End Rem
Type mxModuleBase Abstract

	Field m_name:String

'#region Field accessors

	Rem
		bbdoc: Set the base's name.
		returns: Nothing.
	End Rem
	Method SetName(name:String)
		Assert name, "(mxModuleBase.SetName) name cannot be Null"
		m_name = name
	End Method

	Rem
		bbdoc: Get the base's name.
		returns: The base's name.
	End Rem
	Method GetName:String()
		Return m_name
	End Method

'#end region Field accessors

	Rem
		bbdoc: Set a common field from the given variable.
		returns: True if the given variable was handled, or False if it was not.
	End Rem
	Method SetCommonFromVariable:Int(variable:dVariable)
		Return False
	End Method

End Type

Rem
	bbdoc: Maximus module scope.
End Rem
Type mxModuleScope Extends mxModuleBase

	Field m_modules:dObjectMap

	Method New()
		m_modules = New dObjectMap
	End Method

	Rem
		bbdoc: Add the given module to the scope.
		returns: True if the module was added, or False if it was not (the module is Null).
	End Rem
	Method AddModule:Int(modul:mxModule)
		If modul
			m_modules._Insert(modul.GetName(), modul)
			modul.SetParent(Self)
			Return True
		End If
		Return False
	End Method

	Rem
		bbdoc: Check if the given module name is found in the scope.
		returns: True if the given module was found in the scope, or False if it was not.
	End Rem
	Method HasModule:Int(modname:String)
		Return m_modules._Contains(modname)
	End Method

	Rem
		bbdoc: Get a module with the given name.
		returns: The module with the given name, or Null if there is no module with the given name.
	End Rem
	Method GetModuleWithName:mxModule(modname:String)
		Return mxModule(m_modules._ObjectWithKey(modname))
	End Method

	Rem
		bbdoc: Load the given dJObject into the scope.
		returns: Itself, or Null if @root is Null.
	End Rem
	Method FromJSON:mxModuleScope(root:dJObject)
		If root
			SetName(root.GetName())
			For Local variable:dVariable = EachIn root
				If dJObject(variable)
					AddModule(New mxModule.FromJSON(dJObject(variable)))
				'Else
				'	SetCommonFromVariable(variable)
				End If
			Next
			Return Self
		End If
		Return Null
	End Method

	Rem
		bbdoc: Get the module enumerator for the scope.
		returns: The module enumerator for the scope.
	End Rem
	Method ModuleEnumerator:TMapEnumerator()
		Return m_modules.ValueEnumerator()
	End Method

End Type

Rem
	bbdoc: Maximus module.
End Rem
Type mxModule Extends mxModuleBase

	Field m_parent:mxModuleScope
	Field m_description:String
	Field m_versions:dObjectMap

	Method New()
		m_versions = New dObjectMap
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the module's parent.
		returns: Nothing.
	End Rem
	Method SetParent(parent:mxModuleScope)
		m_parent = parent
	End Method

	Rem
		bbdoc: Get the module's parent.
		returns: The module's parent.
	End Rem
	Method GetParent:mxModuleScope()
		Return m_parent
	End Method

	Rem
		bbdoc: Get the full name of the module (e.g. "modscope.module").
		returns: The full name of the module.
	End Rem
	Method GetFullName:String()
		Return m_parent.m_name + "." + m_name
	End Method

	Rem
		bbdoc: Set the module's description.
		returns: Nothing.
	End Rem
	Method SetDescription(description:String)
		m_description = description
	End Method

	Rem
		bbdoc: Get the module's description.
		returns: The module's description.
	End Rem
	Method GetDescription:String()
		Return m_description
	End Method

'#end region Field accessors

	Rem
		bbdoc: Get the installed version of the module (if it is installed).
		returns: The version of the module that is installed, or Null if the module is not installed.
	End Rem
	Method GetInstalledVersion:mxModuleVersion()
		Local version:String = mxModUtils.GetInstalledVersionFromVerID(GetFullName())
		If version
			Return New mxModuleVersion.Create(Self, version, Null)
		End If
		Return Null
	End Method

	Rem
		bbdoc: Add the given version to the module.
		returns: True if the version was added, or False if it was not (the version is Null).
	End Rem
	Method AddVersion:Int(version:mxModuleVersion)
		If version
			m_versions._Insert(version.GetName(), version)
			version.SetParent(Self)
			Return True
		End If
		Return False
	End Method

	Rem
		bbdoc: Check if there is a version with the given name.
		returns: True if the version was found, or False if it was not.
	End Rem
	Method HasVersion:Int(name:String)
		Return m_versions._Contains(name)
	End Method

	Rem
		bbdoc: Get a version with the given name.
		returns: The version with the given name, or Null if there is no version with the given name.
	End Rem
	Method GetVersionWithName:mxModuleVersion(name:String)
		Return mxModuleVersion(m_versions._ObjectWithKey(name))
	End Method

	Rem
		bbdoc: Get the latest (non-dev if available) version for this module.
		returns: The latest version for this module.
		about: The latest version will be returned, or the dev version if it is the only version.
	End Rem
	Method GetLatestVersion:mxModuleVersion()
		Local hver:mxModuleVersion
		For Local ver:mxModuleVersion = EachIn VersionEnumerator()
			If ver.GetName() <> "dev"
				If Not hver Or ver.Compare(hver) = 1
					hver = ver
				End If
			End If
		Next
		If Not hver
			hver = GetVersionWithName("dev")
		End If
		Return hver
	End Method

	Rem
		bbdoc: Set a common field from the given variable.
		returns: True if the given variable was handled, or False if it was not.
	End Rem
	Method SetCommonFromVariable:Int(variable:dVariable)
		If Super.SetCommonFromVariable(variable)
			Return True
		Else
			Select variable.GetName().ToLower()
				Case "desc"
					SetDescription(dValueVariable(variable).ValueAsString())
				Case "versions"
					For Local jobj:dJObject = EachIn dJObject(variable)
						AddVersion(New mxModuleVersion.FromJSON(jobj))
					Next
				Default
					Return False
			End Select
			Return True
		End If
	End Method

	Rem
		bbdoc: Load the given dJObject into the module.
		returns: Itself, or Null if @root is Null.
	End Rem
	Method FromJSON:mxModule(root:dJObject)
		If root
			SetName(root.GetName())
			For Local variable:dVariable = EachIn root
				SetCommonFromVariable(variable)
			Next
			Return Self
		End If
		Return Null
	End Method

	Rem
		bbdoc: Get the version enumerator for the module.
		returns: The version enumerator for the module.
	End Rem
	Method VersionEnumerator:TMapEnumerator()
		Return m_versions.ValueEnumerator()
	End Method

End Type

Rem
	bbdoc: Maximus module version.
End Rem
Type mxModuleVersion

	Field m_parent:mxModule
	Field m_name:String, m_url:String
	Field m_dependencies:mxModuleDependencies

	Method New()
		m_dependencies = New mxModuleDependencies
	End Method

	Rem
		bbdoc: Create a module version.
		returns: Itself.
	End Rem
	Method Create:mxModuleVersion(parent:mxModule, name:String, url:String)
		SetParent(parent)
		SetName(name)
		SetUrl(url)
		Return Self
	End Method

	Rem
		bbdoc: Clone the version.
		returns: A clone of the version.
		about: If @withdeps is True (default value), the dependencies will be added as well.
	End Rem
	Method Clone:mxModuleVersion(withdeps:Int = True)
		Local v:mxModuleVersion = New mxModuleVersion.Create(m_parent, m_name, m_url)
		For Local dep:mxModuleDependency = EachIn DependencyEnumerator()
			v.m_dependencies.AddDependency(dep)
		Next
		Return v
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the version's parent.
		returns: Nothing.
	End Rem
	Method SetParent(parent:mxModule)
		m_parent = parent
	End Method

	Rem
		bbdoc: Get the version's parent.
		returns: The version's parent.
	End Rem
	Method GetParent:mxModule()
		Return m_parent
	End Method

	Rem
		bbdoc: Set the version's name.
		returns: Nothing.
	End Rem
	Method SetName(name:String)
		Assert name, "(mxModuleVersion.SetName) name cannot be Null!"
		m_name = name
	End Method

	Rem
		bbdoc: Get the version's name.
		returns: The version's name.
	End Rem
	Method GetName:String()
		Return m_name
	End Method

	Rem
		bbdoc: Set the version's url.
		returns: Nothing.
	End Rem
	Method SetUrl(url:String)
		'Assert url, "(mxModuleVersion.SetUrl) url cannot be Null!"
		m_url = url
	End Method

	Rem
		bbdoc: Get the version's url.
		returns: The version's url.
	End Rem
	Method GetUrl:String()
		Return m_url
	End Method

	Rem
		bbdoc: Get the temporary fetch path for the version's source archive.
		returns: The temporary file path for the version's source archive.
	End Rem
	Method GetTemporaryFilePath:String()
		Return "tmp/" + m_parent.GetFullName() + "-" + m_name + ".zip"
	End Method

	Rem
		bbdoc: Set the version's dependencies.
		returns: Nothing.
	End Rem
	Method SetDependencies(dependencies:mxModuleDependencies)
		m_dependencies = dependencies
	End Method

	Rem
		bbdoc: Get the version's dependencies.
		returns: The version's dependencies.
	End Rem
	Method GetDependencies:mxModuleDependencies()
		Return m_dependencies
	End Method

'#end region Field accessors

	Rem
		bbdoc: Set a common field from the given variable.
		returns: True if the given variable was handled, or False if it was not.
	End Rem
	Method SetCommonFromVariable:Int(variable:dVariable)
		Select variable.GetName().ToLower()
			Case "url"
				SetUrl(dValueVariable(variable).ValueAsString())
			Case "deps"
				m_dependencies.FromJSON(dJArray(variable))
			Default
				Return False
		End Select
		Return True
	End Method

	Rem
		bbdoc: Load the given dJObject into the version.
		returns: Itself, or Null if @root is Null.
	End Rem
	Method FromJSON:mxModuleVersion(root:dJObject)
		If root
			SetName(root.GetName())
			For Local variable:dVariable = EachIn root
				SetCommonFromVariable(variable)
			Next
			Return Self
		End If
		Return Null
	End Method

	Rem
		bbdoc: Get the dependency enumerator for the version.
		returns: The dependency enumerator for the version.
	End Rem
	Method DependencyEnumerator:TMapEnumerator()
		Return m_dependencies.DependencyEnumerator()
	End Method

	Rem
		bbdoc: Compare the version with the given version.
		returns: 0 if the two versions are the same (same in version number alone), 1 if this version is greater than the given, or -1 if the given version is greater than this version.
	End Rem
	Method Compare:Int(with:Object)
		Local ver:mxModuleVersion = mxModuleVersion(with)
		If ver
			Local version1:mxVersionObject = New mxVersionObject.Parse(m_name)
			Local version2:mxVersionObject = New mxVersionObject.Parse(ver.m_name)
			Return version1.Compare(version2)
		End If
		Return Super.Compare(with)
	End Method

	Rem
		bbdoc: Fetch the version's source archive.
		returns: Nothing.
	End Rem
	Method FetchSourceArchive()
		Local file:String = GetTemporaryFilePath()
		logger.LogMessage("fetching: " + GetUrl() + " -> " + file + "    ", False)
		If FileType(file) = FILETYPE_NONE
			Local stream:TStream = WriteFileExplicitly(file)
			If stream
				Local request:TRESTRequest = New TRESTRequest, response:TRESTResponse
				request.SetProgressCallback(_ProgressCallback, New _mxProgressStore)
				request.SetStream(stream)
				Try
					response = request.Call(GetUrl(), ["User-Agent: " + mainapp.m_useragent], "GET")
				Catch e:Object
					stream.Close()
					DeleteFile(file)
					logger.LogMessage("")
					ThrowError(_s("error.fetch.archive", [e.ToString()]))
				End Try
				stream.Close()
				If response.responseCode = 200
					logger.LogMessage(_s("message.fetch.done", [String(response.responseCode)]))
				Else
					DeleteFile(file)
					logger.LogMessage("")
					ThrowError(_s("error.fetch.archive", ["Bad response code: " + String(response.responseCode)]))
				End If
			Else
				ThrowError(_s("error.writeperms", [file]))
			End If
		Else
			logger.LogMessage(_s("message.fetch.alreadyfetched"))
		End If
	End Method

	Rem
		bbdoc: Progress callback for source archive fetching.
		returns: Zero (no error).
	End Rem
	Function _ProgressCallback:Int(data:Object, dltotal:Double, dlnow:Double, ultotal:Double, ulnow:Double)
		Local store:_mxProgressStore = _mxProgressStore(data)
		Local prog:Int = (dlnow / dltotal) * 100
		If prog > store.m_progress + 5
			store.m_progress = prog
			WriteStdOut(".")
		End If
		Return 0
	End Function

End Type

Rem
	bbdoc: A object representing a version, not to be confused with mxModuleVersion
	about: Supported formats are 1, 1.1 and 1.1.1.
	Do note that because the version is represented as a Double there's a change
	the precision of the final number is off.

	1.1.2 Would be converted to 1.001002 but when converted to a Double it results
	in 1.0010019999999999.

	1.1.12 However would be converted to 1.001012 and when converted to a Double it
	results in 1.0010120000000000.

	A version string of 'dev' will be represented as 99999
End Rem
Type mxVersionObject

	Field m_version:Double

	Rem
		bbdoc: Parse a version string
		returns: Itself.
	End Rem
	Method Parse:mxVersionObject(version:String)
		If version = "dev"
			m_version = 99999
			Return Self
		End If

		Local parts:String[] = version.Split(".")
		Local converted:String = parts[0]
		If parts.Length > 1
			converted:+"."
			For Local part:String = EachIn parts[1..]
				While part.Length <> 3
					part = "0" + part
				WEnd
				converted:+part
			Next

			'Make sure we format to x.x.x
			If parts.Length = 2
				converted:+"000"
			End If
		End If

		m_version = converted.ToDouble()
		Return Self
	End Method

	Rem
		bbdoc: Compare version numbers
	End Rem
	Method Compare:Int(withObject:Object)
		Local version:mxVersionObject = mxVersionObject(withObject)
		If version
			If m_version = version.m_version
				Return 0
			Else If m_version > version.m_version
				Return 1
			Else If m_version < version.m_version
				Return - 1
			End If
		End If
		Return Super.Compare(withObject)
	End Method
End Type

Rem
	bbdoc: Maximus metafile handler
End Rem
Type mxMetaFile

	Field m_metafile:String
	Field m_scope:String
	Field m_name:String
	Field m_version:String

	Rem
		bbdoc: Create a metafile handler.
		returns: Itself.
	End Rem
	Method Create:mxMetaFile(metafile:String)
		SetMetaFile(metafile)
		Return Self
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the metafile file path.
		returns: Nothing.
	End Rem
	Method SetMetaFile(metafile:String)
		m_metafile = metafile
	End Method

	Rem
		bbdoc: Get the metafile path.
		returns: The metafile path.
	End Rem
	Method GetMetaFile:String()
		Return m_metafile
	End Method

'#end region Field accessors

	Rem
		bbdoc: Load the configuration.
		returns: Nothing.
	End Rem
	Method Load()
		If FileType(GetMetaFile()) = FILETYPE_FILE
			Try
				Local contents:String = LoadText(GetMetaFile()).Trim()
				Local parts:String[] = contents.Split("/")

				If parts.Length <> 2
					Throw _s("error.load.meta.invalid_format") ;
				End If

				Local modinfo:String[] = parts[0].Split(".")
				m_scope = modinfo[0]
				m_name = modinfo[1]
				m_version = parts[1]
			Catch e:Object
				logger.LogError(_s("error.load.metafile.parse", [GetMetaFile()]))
				logger.LogError(_s("error.load.metafile.parse", [e.ToString()]))
			End Try
		Else
			logger.LogWarning(_s("error.load.metafile.notfound", [GetMetaFile()]))
		End If
	End Method

	Rem
		bbdoc: Save the configuration
		returns: Nothing.
	End Rem
	Method Save()
		If m_scope.Length = 0 Or m_name.Length = 0 Or m_version.Length = 0
			ThrowError(_s("error.save.metafile.incomplete", [m_scope, m_name, m_version]))
		End If

		Local stream:TStream = WriteFileExplicitly(GetMetaFile())
		stream.WriteString(m_scope + "." + m_name + "/" + m_version)
		stream.Close
	End Method
End Type

Rem
	bbdoc: Maximus module base.
	about: An abstract type for the similarities between module scopes and modules.
End Rem
Type mxModuleBase Abstract

	Field m_name:String

'#region Field accessors

	Rem
		bbdoc: Set the base's name.
		returns: Nothing.
	End Rem
	Method SetName(name:String)
		Assert name, "(mxModuleBase.SetName) name cannot be Null"
		m_name = name
	End Method

	Rem
		bbdoc: Get the base's name.
		returns: The base's name.
	End Rem
	Method GetName:String()
		Return m_name
	End Method

'#end region Field accessors

	Rem
		bbdoc: Set a common field from the given variable.
		returns: True if the given variable was handled, or False if it was not.
	End Rem
	Method SetCommonFromVariable:Int(variable:dVariable)
		Return False
	End Method

End Type

Rem
	bbdoc: Maximus module scope.
End Rem
Type mxModuleScope Extends mxModuleBase

	Field m_modules:dObjectMap

	Method New()
		m_modules = New dObjectMap
	End Method

	Rem
		bbdoc: Add the given module to the scope.
		returns: True if the module was added, or False if it was not (the module is Null).
	End Rem
	Method AddModule:Int(modul:mxModule)
		If modul
			m_modules._Insert(modul.GetName(), modul)
			modul.SetParent(Self)
			Return True
		End If
		Return False
	End Method

	Rem
		bbdoc: Check if the given module name is found in the scope.
		returns: True if the given module was found in the scope, or False if it was not.
	End Rem
	Method HasModule:Int(modname:String)
		Return m_modules._Contains(modname)
	End Method

	Rem
		bbdoc: Get a module with the given name.
		returns: The module with the given name, or Null if there is no module with the given name.
	End Rem
	Method GetModuleWithName:mxModule(modname:String)
		Return mxModule(m_modules._ObjectWithKey(modname))
	End Method

	Rem
		bbdoc: Load the given dJObject into the scope.
		returns: Itself, or Null if @root is Null.
	End Rem
	Method FromJSON:mxModuleScope(root:dJObject)
		If root
			SetName(root.GetName())
			For Local variable:dVariable = EachIn root
				If dJObject(variable)
					AddModule(New mxModule.FromJSON(dJObject(variable)))
				'Else
				'	SetCommonFromVariable(variable)
				End If
			Next
			Return Self
		End If
		Return Null
	End Method

	Rem
		bbdoc: Get the module enumerator for the scope.
		returns: The module enumerator for the scope.
	End Rem
	Method ModuleEnumerator:TMapEnumerator()
		Return m_modules.ValueEnumerator()
	End Method

End Type

Rem
	bbdoc: Maximus module.
End Rem
Type mxModule Extends mxModuleBase

	Field m_parent:mxModuleScope
	Field m_description:String
	Field m_versions:dObjectMap

	Method New()
		m_versions = New dObjectMap
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the module's parent.
		returns: Nothing.
	End Rem
	Method SetParent(parent:mxModuleScope)
		m_parent = parent
	End Method

	Rem
		bbdoc: Get the module's parent.
		returns: The module's parent.
	End Rem
	Method GetParent:mxModuleScope()
		Return m_parent
	End Method

	Rem
		bbdoc: Get the full name of the module (e.g. "modscope.module").
		returns: The full name of the module.
	End Rem
	Method GetFullName:String()
		Return m_parent.m_name + "." + m_name
	End Method

	Rem
		bbdoc: Set the module's description.
		returns: Nothing.
	End Rem
	Method SetDescription(description:String)
		m_description = description
	End Method

	Rem
		bbdoc: Get the module's description.
		returns: The module's description.
	End Rem
	Method GetDescription:String()
		Return m_description
	End Method

'#end region Field accessors

	Rem
		bbdoc: Get the installed version of the module (if it is installed).
		returns: The version of the module that is installed, or Null if the module is not installed.
	End Rem
	Method GetInstalledVersion:mxModuleVersion()
		Local version:String = mxModUtils.GetInstalledVersionFromVerID(GetFullName())
		If version
			Return New mxModuleVersion.Create(Self, version, Null)
		End If
		Return Null
	End Method

	Rem
		bbdoc: Add the given version to the module.
		returns: True if the version was added, or False if it was not (the version is Null).
	End Rem
	Method AddVersion:Int(version:mxModuleVersion)
		If version
			m_versions._Insert(version.GetName(), version)
			version.SetParent(Self)
			Return True
		End If
		Return False
	End Method

	Rem
		bbdoc: Check if there is a version with the given name.
		returns: True if the version was found, or False if it was not.
	End Rem
	Method HasVersion:Int(name:String)
		Return m_versions._Contains(name)
	End Method

	Rem
		bbdoc: Get a version with the given name.
		returns: The version with the given name, or Null if there is no version with the given name.
	End Rem
	Method GetVersionWithName:mxModuleVersion(name:String)
		Return mxModuleVersion(m_versions._ObjectWithKey(name))
	End Method

	Rem
		bbdoc: Get the latest (non-dev if available) version for this module.
		returns: The latest version for this module.
		about: The latest version will be returned, or the dev version if it is the only version.
	End Rem
	Method GetLatestVersion:mxModuleVersion()
		Local hver:mxModuleVersion
		For Local ver:mxModuleVersion = EachIn VersionEnumerator()
			If ver.GetName() <> "dev"
				If Not hver Or ver.Compare(hver) = 1
					hver = ver
				End If
			End If
		Next
		If Not hver
			hver = GetVersionWithName("dev")
		End If
		Return hver
	End Method

	Rem
		bbdoc: Set a common field from the given variable.
		returns: True if the given variable was handled, or False if it was not.
	End Rem
	Method SetCommonFromVariable:Int(variable:dVariable)
		If Super.SetCommonFromVariable(variable)
			Return True
		Else
			Select variable.GetName().ToLower()
				Case "desc"
					SetDescription(dValueVariable(variable).ValueAsString())
				Case "versions"
					For Local jobj:dJObject = EachIn dJObject(variable)
						AddVersion(New mxModuleVersion.FromJSON(jobj))
					Next
				Default
					Return False
			End Select
			Return True
		End If
	End Method

	Rem
		bbdoc: Load the given dJObject into the module.
		returns: Itself, or Null if @root is Null.
	End Rem
	Method FromJSON:mxModule(root:dJObject)
		If root
			SetName(root.GetName())
			For Local variable:dVariable = EachIn root
				SetCommonFromVariable(variable)
			Next
			Return Self
		End If
		Return Null
	End Method

	Rem
		bbdoc: Get the version enumerator for the module.
		returns: The version enumerator for the module.
	End Rem
	Method VersionEnumerator:TMapEnumerator()
		Return m_versions.ValueEnumerator()
	End Method

End Type

Rem
	bbdoc: Maximus module version.
End Rem
Type mxModuleVersion

	Field m_parent:mxModule
	Field m_name:String, m_url:String
	Field m_dependencies:mxModuleDependencies

	Method New()
		m_dependencies = New mxModuleDependencies
	End Method

	Rem
		bbdoc: Create a module version.
		returns: Itself.
	End Rem
	Method Create:mxModuleVersion(parent:mxModule, name:String, url:String)
		SetParent(parent)
		SetName(name)
		SetUrl(url)
		Return Self
	End Method

	Rem
		bbdoc: Clone the version.
		returns: A clone of the version.
		about: If @withdeps is True (default value), the dependencies will be added as well.
	End Rem
	Method Clone:mxModuleVersion(withdeps:Int = True)
		Local v:mxModuleVersion = New mxModuleVersion.Create(m_parent, m_name, m_url)
		For Local dep:mxModuleDependency = EachIn DependencyEnumerator()
			v.m_dependencies.AddDependency(dep)
		Next
		Return v
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the version's parent.
		returns: Nothing.
	End Rem
	Method SetParent(parent:mxModule)
		m_parent = parent
	End Method

	Rem
		bbdoc: Get the version's parent.
		returns: The version's parent.
	End Rem
	Method GetParent:mxModule()
		Return m_parent
	End Method

	Rem
		bbdoc: Set the version's name.
		returns: Nothing.
	End Rem
	Method SetName(name:String)
		Assert name, "(mxModuleVersion.SetName) name cannot be Null!"
		m_name = name
	End Method

	Rem
		bbdoc: Get the version's name.
		returns: The version's name.
	End Rem
	Method GetName:String()
		Return m_name
	End Method

	Rem
		bbdoc: Set the version's url.
		returns: Nothing.
	End Rem
	Method SetUrl(url:String)
		'Assert url, "(mxModuleVersion.SetUrl) url cannot be Null!"
		m_url = url
	End Method

	Rem
		bbdoc: Get the version's url.
		returns: The version's url.
	End Rem
	Method GetUrl:String()
		Return m_url
	End Method

	Rem
		bbdoc: Get the temporary fetch path for the version's source archive.
		returns: The temporary file path for the version's source archive.
	End Rem
	Method GetTemporaryFilePath:String()
		Return "tmp/" + m_parent.GetFullName() + "-" + m_name + ".zip"
	End Method

	Rem
		bbdoc: Set the version's dependencies.
		returns: Nothing.
	End Rem
	Method SetDependencies(dependencies:mxModuleDependencies)
		m_dependencies = dependencies
	End Method

	Rem
		bbdoc: Get the version's dependencies.
		returns: The version's dependencies.
	End Rem
	Method GetDependencies:mxModuleDependencies()
		Return m_dependencies
	End Method

	Rem
		bbdoc: Get the version parts for the version.
		returns: True if the version is development, in which case the parameters are both 0; or False, in which case the parameters are set accordingly.
	End Rem
	Method GetVersionParts:Int(vmajor:String Var, vminor:String Var)
		If m_name <> "dev"
			Local i:Int = m_name.Find(".")
			If i > -1
				vmajor = m_name[..i]
				vminor = m_name[i + 1..]
			Else
				vmajor = m_name ' I'm not sure what other version formats would be used, so I'm just playing a random card here
			End If
			Return False
		End If
		Return True
	End Method

'#end region Field accessors

	Rem
		bbdoc: Set a common field from the given variable.
		returns: True if the given variable was handled, or False if it was not.
	End Rem
	Method SetCommonFromVariable:Int(variable:dVariable)
		Select variable.GetName().ToLower()
			Case "url"
				SetUrl(dValueVariable(variable).ValueAsString())
			Case "deps"
				m_dependencies.FromJSON(dJArray(variable))
			Default
				Return False
		End Select
		Return True
	End Method

	Rem
		bbdoc: Load the given dJObject into the version.
		returns: Itself, or Null if @root is Null.
	End Rem
	Method FromJSON:mxModuleVersion(root:dJObject)
		If root
			SetName(root.GetName())
			For Local variable:dVariable = EachIn root
				SetCommonFromVariable(variable)
			Next
			Return Self
		End If
		Return Null
	End Method

	Rem
		bbdoc: Get the dependency enumerator for the version.
		returns: The dependency enumerator for the version.
	End Rem
	Method DependencyEnumerator:TMapEnumerator()
		Return m_dependencies.DependencyEnumerator()
	End Method

	Rem
		bbdoc: Compare the version with the given version.
		returns: 0 if the two versions are the same (same in version number alone), 1 if this version is greater than the given, or -1 if the given version is greater than this version.
	End Rem
	Method Compare:Int(with:Object)
		Local ver:mxModuleVersion = mxModuleVersion(with)
		If ver
			If m_name = ver.m_name
				Return 0
			Else
				Local smajor:String, sminor:String, wmajor:String, wminor:String
				Local sdev:Int = GetVersionParts(smajor, sminor), wdev:Int = ver.GetVersionParts(wmajor, wminor)
				If (sdev And wdev) Or (smajor = wmajor And sminor = wminor)
					Return 0
				Else If smajor > wmajor
					Return 1
				Else If smajor = wmajor
					If sminor > wminor
						Return 1
					Else
						Return -1
					End If
				Else
					Return -1
				End If
			End If
		End If
		Return Super.Compare(with)
	End Method

	Rem
		bbdoc: Fetch the version's source archive.
		returns: Nothing.
	End Rem
	Method FetchSourceArchive()
		Local file:String = GetTemporaryFilePath()
		logger.LogMessage("fetching: " + GetUrl() + " -> " + file + "    ", False)
		If FileType(file) = FILETYPE_NONE
			Local stream:TStream = WriteFileExplicitly(file)
			If stream
				Local request:TRESTRequest = New TRESTRequest, response:TRESTResponse
				request.SetProgressCallback(_ProgressCallback, New _mxProgressStore)
				request.SetStream(stream)
				Try
					response = request.Call(GetUrl(), ["User-Agent: " + mainapp.m_useragent], "GET")
				Catch e:Object
					stream.Close()
					DeleteFile(file)
					logger.LogMessage("")
					ThrowError(_s("error.fetch.archive", [e.ToString()]))
				End Try
				stream.Close()
				If response.responseCode = 200
					logger.LogMessage(_s("message.fetch.done", [String(response.responseCode)]))
				Else
					DeleteFile(file)
					logger.LogMessage("")
					ThrowError(_s("error.fetch.archive", ["Bad response code: " + String(response.responseCode)]))
				End If
			Else
				ThrowError(_s("error.writeperms", [file]))
			End If
		Else
			logger.LogMessage(_s("message.fetch.alreadyfetched"))
		End If
	End Method

	Rem
		bbdoc: Progress callback for source archive fetching.
		returns: Zero (no error).
	End Rem
	Function _ProgressCallback:Int(data:Object, dltotal:Double, dlnow:Double, ultotal:Double, ulnow:Double)
		Local store:_mxProgressStore = _mxProgressStore(data)
		Local prog:Int = (dlnow / dltotal) * 100
		If prog > store.m_progress + 5
			store.m_progress = prog
			WriteStdOut(".")
		End If
		Return 0
	End Function

End Type

Rem
	bbdoc: A object representing a version, not to be confused with mxModuleVersion
	about: Supported formats are 1, 1.1 and 1.1.1.
	Do note that because the version is represented as a Double there's a change
	the precision of the final number is off.

	1.1.2 Would be converted to 1.001002 but when converted to a Double it results
	in 1.0010019999999999.

	1.1.12 However would be converted to 1.001012 and when converted to a Double it
	results in 1.0010120000000000.

	A version string of 'dev' will be represented as 99999
End Rem
Type mxVersionObject

	Field m_version:Double

	Rem
		bbdoc: Parse a version string
		returns: Itself.
	End Rem
	Method Parse:mxVersionObject(version:String)
		If version = "dev"
			m_version = 99999
			Return Self
		End If

		Local parts:String[] = version.Split(".")
		Local converted:String = parts[0]
		If parts.Length > 1
			converted:+"."
			For Local part:String = EachIn parts[1..]
				While part.Length <> 3
					part = "0" + part
				WEnd
				converted:+part
			Next

			'Make sure we format to x.x.x
			If parts.Length = 2
				converted:+"000"
			End If
		End If

		m_version = converted.ToDouble()
		Return Self
	End Method

	Rem
		bbdoc: Compare version numbers
	End Rem
	Method Compare:Int(withObject:Object)
		Local version:mxVersionObject = mxVersionObject(withObject)
		If version
			If m_version = version.m_version
				Return 0
			Else If m_version > version.m_version
				Return 1
			Else If m_version < version.m_version
				Return - 1
			End If
		End If
		Return Super.Compare(withObject)
	End Method
End Type

Rem
	bbdoc: Maximus metafile handler
End Rem
Type mxMetaFile

	Field m_metafile:String
	Field m_scope:String
	Field m_name:String
	Field m_version:String

	Rem
		bbdoc: Create a metafile handler.
		returns: Itself.
	End Rem
	Method Create:mxMetaFile(metafile:String)
		SetMetaFile(metafile)
		Return Self
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the metafile file path.
		returns: Nothing.
	End Rem
	Method SetMetaFile(metafile:String)
		m_metafile = metafile
	End Method

	Rem
		bbdoc: Get the metafile path.
		returns: The metafile path.
	End Rem
	Method GetMetaFile:String()
		Return m_metafile
	End Method

'#end region Field accessors

	Rem
		bbdoc: Load the configuration.
		returns: Nothing.
	End Rem
	Method Load()
		If FileType(GetMetaFile()) = FILETYPE_FILE
			Try
				Local contents:String = LoadText(GetMetaFile()).Trim()
				Local parts:String[] = contents.Split("/")

				If parts.Length <> 2
					Throw _s("error.load.meta.invalid_format") ;
				End If

				Local modinfo:String[] = parts[0].Split(".")
				m_scope = modinfo[0]
				m_name = modinfo[1]
				m_version = parts[1]
			Catch e:Object
				logger.LogError(_s("error.load.metafile.parse", [GetMetaFile()]))
				logger.LogError(_s("error.load.metafile.parse", [e.ToString()]))
			End Try
		Else
			logger.LogWarning(_s("error.load.metafile.notfound", [GetMetaFile()]))
		End If
	End Method

	Rem
		bbdoc: Save the configuration
		returns: Nothing.
	End Rem
	Method Save()
		If m_scope.Length = 0 Or m_name.Length = 0 Or m_version.Length = 0
			ThrowError(_s("error.save.metafile.incomplete", [m_scope, m_name, m_version]))
		End If

		Local stream:TStream = WriteFileExplicitly(GetMetaFile())
		stream.WriteString(m_scope + "." + m_name + "/" + m_version)
		stream.Close
	End Method
End Type


Removing last remnants of the old shader system.

Strict

Rem
	bbdoc: MaxB3D shader drivers
End Rem
Module MaxB3D.Shaders
ModuleInfo "Author: Kevin Primm"
ModuleInfo "License: MIT"

Import MaxB3D.GLSL
Import MaxB3D.CgSL