MonkeyMax - Added OnSuspend/OnResume for gxtkAudio

' ***** 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
		Try
			Return LoadText( path )
		Catch ReadFail:Object
			Return Null
		EndTry
	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+iy*iy)
		sy = Sqr(jx*jx+jy*jy)
		rot = Atan2( iy, ix )
		If ix < 0 Then
			sx = -sx
			rot :+ 180
		EndIf
		If jy < 0 Then sy = -sy
		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]
	Const MUSIC_CHANNEL:Int = 32
	Field music:gxtkSample

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

	Method OnSuspend()
		For Local i:Int = 0 To 33 - 1
			Local chan:gxtkChannel = channels[i]
			If chan.state = 1 Then
				chan.channel.SetPaused(True)
			EndIf
		Next
	End Method

	Method OnResume()
		For Local i:Int = 0 To 33 - 1
			Local chan:gxtkChannel = channels[i]
			If chan.state = 1 Then
				chan.channel.SetPaused(False)
			EndIf
		Next
	End Method

	Method MusicState:Int()
		' Monkey Docs: 	0 if the music is currently stopped
		'				1 if the music is currently playing
		'				-1 if the music state cannot be determined

		Local chan:gxtkChannel = channels[MUSIC_CHANNEL]
		If chan.channel <> Null Then
			If ChannelPlaying(chan.channel) Then Return 1 Else Return 0
		EndIf

		Return -1
	EndMethod

	Method PlayMusic:Int( path:String, flags:Int )
		StopMusic()
		music = LoadSample( path )
		If Not music Then Return -1

		PlaySample(music, MUSIC_CHANNEL, flags)
		Return 0
	EndMethod

	Method StopMusic:Int()
		StopChannel( MUSIC_CHANNEL )

		If music Then
			music.Discard()
			music = null
		EndIf
		Return 0
	EndMethod

	Method ChannelState:int( channel:int )
		' Monkey Docs: 	0 if the channel is currently stopped
		'				1 if the channel is currently playing
		'				2 if the channel is currently paused
		'				-1 if the channel state cannot be determined
		Local chan:gxtkChannel = channels[channel]
		If chan.channel <> Null Then
			If ChannelPlaying(chan.channel) Then Return 1 Else Return 0
		EndIf

		Return -1
	EndMethod

	Method StopChannel( channel:Int )
		Local chan:gxtkChannel = channels[channel]

		If chan.state <> 0
			chan.channel.Stop()
			chan.channel=null
			chan.sample=null
			chan.state=0
		EndIf
	EndMethod

	Method PauseChannel:Int( channel:Int)
		Local chan:gxtkChannel = channels[channel]
		chan.channel.SetPaused(True)
		Return 0
	EndMethod

	Method ResumeChannel:Int( channel:Int)
		Local chan:gxtkChannel = channels[channel]
		chan.channel.SetPaused(False)
		Return 0
	EndMethod

	Method PlaySample( sound:gxtkSample, channel:Int, flags:Int )
		Local chan:gxtkChannel = channels[channel]
		If chan.state <> 0 Then chan.channel.SetPaused(True)

		chan.sample = sound
		chan.loops = flags
		chan.state = 1

		' if looping, we need to reload the sound with the looping flag
		If flags <> sound.loop
			Local smp:TSound = LoadSound(sound.path, flags)
			sound.setSound(smp)
			sound.loop = flags
		EndIf

		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()
		Local chan:gxtkChannel = channels[MUSIC_CHANNEL]
		chan.channel.SetPaused(True)
		Return 0
	EndMethod

	Method ResumeMusic:Int()
		Local chan:gxtkChannel = channels[MUSIC_CHANNEL]
		chan.channel.SetPaused(False)
		Return 0
	EndMethod

	Method SetMusicVolume:Int( volume:Float )
		SetVolume( MUSIC_CHANNEL, volume )
		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)
				gs.path = "data/"+path
				Return gs
			EndIf
		Else
			RuntimeError "BlitzMax can only use ogg and wav file formats"
		EndIf
		Return Null
	EndMethod
EndType

Type gxtkSample
	Field sound:TSound
	Field path:String
	Field loop:Int

	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()
			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
		Try
			Return LoadText( path )
		Catch ReadFail:Object
			Return Null
		EndTry
	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+iy*iy)
		sy = Sqr(jx*jx+jy*jy)
		rot = Atan2( iy, ix )
		If ix < 0 Then
			sx = -sx
			rot :+ 180
		EndIf
		If jy < 0 Then sy = -sy
		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]
	Const MUSIC_CHANNEL:Int = 32
	Field music:gxtkSample

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

	Method MusicState:Int()
		' Monkey Docs: 	0 if the music is currently stopped
		'				1 if the music is currently playing
		'				-1 if the music state cannot be determined

		Local chan:gxtkChannel = channels[MUSIC_CHANNEL]
		If chan.channel <> Null Then
			If ChannelPlaying(chan.channel) Then Return 1 Else Return 0
		EndIf

		Return -1
	EndMethod

	Method PlayMusic:Int( path:String, flags:Int )
		StopMusic()
		music = LoadSample( path )
		If Not music Then Return -1

		PlaySample(music, MUSIC_CHANNEL, flags)
		Return 0
	EndMethod

	Method StopMusic:Int()
		StopChannel( MUSIC_CHANNEL )

		If music Then
			music.Discard()
			music = null
		EndIf
		Return 0
	EndMethod

	Method ChannelState:int( channel:int )
		' Monkey Docs: 	0 if the channel is currently stopped
		'				1 if the channel is currently playing
		'				2 if the channel is currently paused
		'				-1 if the channel state cannot be determined
		Local chan:gxtkChannel = channels[channel]
		If chan.channel <> Null Then
			If ChannelPlaying(chan.channel) Then Return 1 Else Return 0
		EndIf

		Return -1
	EndMethod

	Method StopChannel( channel:Int )
		Local chan:gxtkChannel = channels[channel]

		If chan.state <> 0
			chan.channel.Stop()
			chan.channel=null
			chan.sample=null
			chan.state=0
		EndIf
	EndMethod

	Method PauseChannel:Int( channel:Int)
		Local chan:gxtkChannel = channels[channel]
		chan.channel.SetPaused(True)
		Return 0
	EndMethod

	Method ResumeChannel:Int( channel:Int)
		Local chan:gxtkChannel = channels[channel]
		chan.channel.SetPaused(False)
		Return 0
	EndMethod

	Method PlaySample( sound:gxtkSample, channel:Int, flags:Int )
		Local chan:gxtkChannel = channels[channel]
		If chan.state <> 0 Then chan.channel.SetPaused(True)

		chan.sample = sound
		chan.loops = flags
		chan.state = 1

		' if looping, we need to reload the sound with the looping flag
		If flags <> sound.loop
			Local smp:TSound = LoadSound(sound.path, flags)
			sound.setSound(smp)
			sound.loop = flags
		EndIf

		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()
		Local chan:gxtkChannel = channels[MUSIC_CHANNEL]
		chan.channel.SetPaused(True)
		Return 0
	EndMethod

	Method ResumeMusic:Int()
		Local chan:gxtkChannel = channels[MUSIC_CHANNEL]
		chan.channel.SetPaused(False)
		Return 0
	EndMethod

	Method SetMusicVolume:Int( volume:Float )
		SetVolume( MUSIC_CHANNEL, volume )
		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)
				gs.path = "data/"+path
				Return gs
			EndIf
		Else
			RuntimeError "BlitzMax can only use ogg and wav file formats"
		EndIf
		Return Null
	EndMethod
EndType

Type gxtkSample
	Field sound:TSound
	Field path:String
	Field loop:Int

	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 ******


Wrapped LoadText with a Try/Catch to return Null

--HG--
extra : convert_revision : svn%3Ae934d3cd-0d57-f34a-94d0-9e8d31ae1c29/trunk%40395

' ***** 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
		Try
			Return LoadText( path )
		Catch ReadFail:Object
			Return Null
		EndTry
	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+iy*iy)
		sy = Sqr(jx*jx+jy*jy)
		rot = Atan2( iy, ix )
		If ix < 0 Then
			sx = -sx
			rot :+ 180
		EndIf
		If jy < 0 Then sy = -sy
		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()
			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+iy*iy)
		sy = Sqr(jx*jx+jy*jy)
		rot = Atan2( iy, ix )
		If ix < 0 Then
			sx = -sx
			rot :+ 180
		EndIf
		If jy < 0 Then sy = -sy
		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 ******


Changelog update

Changelog update

Rem
	Copyright (c) 2012 Christiaan Kras

	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

SuperStrict

Rem
	bbdoc: htbaapub.zmq
	about: This module is still an early work in progress. If you run into any
	bugs please report them at https://github.com/Htbaa/zmq.mod/issues.
EndRem
Module htbaapub.zmq
ModuleInfo "Name: htbaapub.zmq"
ModuleInfo "Description: A wrapper for ZeroMQ"
ModuleInfo "Version: 0.03"
ModuleInfo "License: MIT"
ModuleInfo "Author: Christiaan Kras"
ModuleInfo "ZeroMQ: http://www.zeromq.org"
ModuleInfo "ZeroMQ License: LGPL (see contents of ./zeromq)"
ModuleInfo "Git repository: https://github.com/Htbaa/zmq.mod/"
ModuleInfo "Issues: https://github.com/Htbaa/zmq.mod/issues"
ModuleInfo "History: Improved test coverage"
ModuleInfo "History: 0.03"
ModuleInfo "History: Fixed message data corruption for first sent message (gh-3)"
ModuleInfo "History: Added TZMQ_Message.CreateFromString to create messages from a String. The TZMQ_Message.Create signature has also changed to accept a Byte Ptr and a length"
ModuleInfo "History: Fixed potential memory leak in TZMQ_Socket.Send when an exception would be raised"
ModuleInfo "History: Added TZMQ_Socket_Exception and TZMQ_Message_Exception"
ModuleInfo "History: Added a couple of unit tests (bah.maxunit required)"
ModuleInfo "History: 0.02"
ModuleInfo "History: Added more examples; Hello World server and client"
ModuleInfo "History: Added more examples; Weather Update server and client"
ModuleInfo "History: TZMQ_Socket.SetSockOpt() now properly sets options"
ModuleInfo "History: TZMQ_Message.Close() now properly frees up memory"
ModuleInfo "History: 0.01"
ModuleInfo "History: First release of htbaapub.zmq using ZMQ 2.1.11. No support yet for input/output multiplexing with zmq_poll()"

?Linux
	Import "-lzmq"
?Win32
	Import "zeromq/lib/libzmq.dll.a"
?

Import "glue.c"
Include "exception.bmx"
Include "constants.bmx"
Include "context.bmx"
Include "socket.bmx"
Include "message.bmx"

'Glue/Helper functions
Extern "C"

	Rem
		bbdoc: Create an empty zmq_message_t struct
		returns: Returns a pointer to a new zmq_message_t struct
	End Rem
	Function bmx_zmq_message_t:Byte Ptr()

	Rem
		bbdoc: Create a zmq_message_t struct with preset data
		returns: Returns a pointer to a new zmq_message_t struct with given data
	End Rem
	Function bmx_zmq_new_message:Byte Ptr(str$z)

	Rem
		bbdoc: Alias for free(), for cleaning up data structures
	End Rem
	Function bmx_zmq_free(msg:Byte Ptr)
End Extern

'ZeroMQ functions
Extern
	Function zmq_version(major:Int Var, minor:Int Var, patch:Int Var)

	Function zmq_init:Byte Ptr(io_threads:Int)
	Function zmq_term:Int(context:Byte Ptr)

	Function zmq_errno:Int()
	Function zmq_strerror$z(errnum:Int)

	Function zmq_socket:Byte Ptr(context:Byte Ptr, socket_type:Int)
	Function zmq_close:Int(s:Byte Ptr)
	Function zmq_setsockopt:Int(s:Byte Ptr, option:Int, optval$z, length:Int)
	Function zmq_getsockopt:Int(s:Byte Ptr, option:Int, optval:Byte Ptr, length:Int Var)
	'ZMQ_EXPORT Int zmq_getsockopt (void *s, Int option, void *optval, size_t *optvallen);
	Function zmq_bind(s:Byte Ptr, addr$z)
	Function zmq_connect:Int(s:Byte Ptr, addr$z)
	Function zmq_send:Int(s:Byte Ptr, msg:Byte Ptr, flags:Int)
	Function zmq_recv:Int(s:Byte Ptr, msg:Byte Ptr, flags:Int)

	Function zmq_msg_init:Int(msg:Byte Ptr)
	Function zmq_msg_init_size:Int(msg:Byte Ptr, size:Long)
	Function zmq_msg_init_data:Int(msg:Byte Ptr, data:Byte Ptr, size:Long, ffn:Byte Ptr, hint:Byte Ptr)
	Function zmq_msg_close:Int(msg:Byte Ptr)
	Function zmq_msg_move:Int(dest:Byte Ptr, src:Byte Ptr)
	Function zmq_msg_copy:Int(dest:Byte Ptr, src:Byte Ptr)
	Function zmq_msg_data:Byte Ptr(msg:Byte Ptr)
	Function zmq_msg_size:Int(msg:Byte Ptr)

	Function zmq_poll:Int(items:Byte Ptr, nitems:Int, timeout:Long)

	Function zmq_device:Int(device:Int, insocket:Byte Ptr, outsocket:Byte Ptr)
End Extern

Rem
	bbdoc: General ZMQ functions
End Rem
Type TZMQ

	Rem
		bbdoc: Get ZMQ Version
		returns: String
	End Rem
	Function Version:String()
		Local major:Int, minor:Int, patch:Int
		zmq_version(major, minor, patch)
		Return major + "." + minor + "." + patch
	End Function

	Rem
		bbdoc: Get current ZMQ error descriptions
		returns: String
	End Rem
	Function Error:String()
		Return zmq_strerror( TZMQ.ErrorNumber() )
	End Function

	Rem
		bbdoc: Get current ZMQ error number
		returns: Int
	End Rem
	Function ErrorNumber:Int()
		Return zmq_errno()
	End Function
End Type

Rem
	Copyright (c) 2012 Christiaan Kras

	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

SuperStrict

Rem
	bbdoc: htbaapub.zmq
	about: This module is still an early work in progress. If you run into any
	bugs please report them at https://github.com/Htbaa/zmq.mod/issues.
EndRem
Module htbaapub.zmq
ModuleInfo "Name: htbaapub.zmq"
ModuleInfo "Description: A wrapper for ZeroMQ"
ModuleInfo "Version: 0.03"
ModuleInfo "License: MIT"
ModuleInfo "Author: Christiaan Kras"
ModuleInfo "ZeroMQ: http://www.zeromq.org"
ModuleInfo "ZeroMQ License: LGPL (see contents of ./zeromq)"
ModuleInfo "Git repository: https://github.com/Htbaa/zmq.mod/"
ModuleInfo "Issues: https://github.com/Htbaa/zmq.mod/issues"
ModuleInfo "History: 0.03"
ModuleInfo "History: Fixed message data corruption for first sent message (gh-3)"
ModuleInfo "History: Added TZMQ_Message.CreateFromString to create messages from a String. The TZMQ_Message.Create signature has also changed to accept a Byte Ptr and a length"
ModuleInfo "History: Fixed potential memory leak in TZMQ_Socket.Send when an exception would be raised"
ModuleInfo "History: Added TZMQ_Socket_Exception and TZMQ_Message_Exception"
ModuleInfo "History: Added a couple of unit tests (bah.maxunit required)"
ModuleInfo "History: 0.02"
ModuleInfo "History: Added more examples; Hello World server and client"
ModuleInfo "History: Added more examples; Weather Update server and client"
ModuleInfo "History: TZMQ_Socket.SetSockOpt() now properly sets options"
ModuleInfo "History: TZMQ_Message.Close() now properly frees up memory"
ModuleInfo "History: 0.01"
ModuleInfo "History: First release of htbaapub.zmq using ZMQ 2.1.11. No support yet for input/output multiplexing with zmq_poll()"

?Linux
	Import "-lzmq"
?Win32
	Import "zeromq/lib/libzmq.dll.a"
?

Import "glue.c"
Include "exception.bmx"
Include "constants.bmx"
Include "context.bmx"
Include "socket.bmx"
Include "message.bmx"

'Glue/Helper functions
Extern "C"

	Rem
		bbdoc: Create an empty zmq_message_t struct
		returns: Returns a pointer to a new zmq_message_t struct
	End Rem
	Function bmx_zmq_message_t:Byte Ptr()

	Rem
		bbdoc: Create a zmq_message_t struct with preset data
		returns: Returns a pointer to a new zmq_message_t struct with given data
	End Rem
	Function bmx_zmq_new_message:Byte Ptr(str$z)

	Rem
		bbdoc: Alias for free(), for cleaning up data structures
	End Rem
	Function bmx_zmq_free(msg:Byte Ptr)
End Extern

'ZeroMQ functions
Extern
	Function zmq_version(major:Int Var, minor:Int Var, patch:Int Var)

	Function zmq_init:Byte Ptr(io_threads:Int)
	Function zmq_term:Int(context:Byte Ptr)

	Function zmq_errno:Int()
	Function zmq_strerror$z(errnum:Int)

	Function zmq_socket:Byte Ptr(context:Byte Ptr, socket_type:Int)
	Function zmq_close:Int(s:Byte Ptr)
	Function zmq_setsockopt:Int(s:Byte Ptr, option:Int, optval$z, length:Int)
	Function zmq_getsockopt:Int(s:Byte Ptr, option:Int, optval:Byte Ptr, length:Int Var)
	'ZMQ_EXPORT Int zmq_getsockopt (void *s, Int option, void *optval, size_t *optvallen);
	Function zmq_bind(s:Byte Ptr, addr$z)
	Function zmq_connect:Int(s:Byte Ptr, addr$z)
	Function zmq_send:Int(s:Byte Ptr, msg:Byte Ptr, flags:Int)
	Function zmq_recv:Int(s:Byte Ptr, msg:Byte Ptr, flags:Int)

	Function zmq_msg_init:Int(msg:Byte Ptr)
	Function zmq_msg_init_size:Int(msg:Byte Ptr, size:Long)
	Function zmq_msg_init_data:Int(msg:Byte Ptr, data:Byte Ptr, size:Long, ffn:Byte Ptr, hint:Byte Ptr)
	Function zmq_msg_close:Int(msg:Byte Ptr)
	Function zmq_msg_move:Int(dest:Byte Ptr, src:Byte Ptr)
	Function zmq_msg_copy:Int(dest:Byte Ptr, src:Byte Ptr)
	Function zmq_msg_data:Byte Ptr(msg:Byte Ptr)
	Function zmq_msg_size:Int(msg:Byte Ptr)

	Function zmq_poll:Int(items:Byte Ptr, nitems:Int, timeout:Long)

	Function zmq_device:Int(device:Int, insocket:Byte Ptr, outsocket:Byte Ptr)
End Extern

Rem
	bbdoc: General ZMQ functions
End Rem
Type TZMQ

	Rem
		bbdoc: Get ZMQ Version
		returns: String
	End Rem
	Function Version:String()
		Local major:Int, minor:Int, patch:Int
		zmq_version(major, minor, patch)
		Return major + "." + minor + "." + patch
	End Function

	Rem
		bbdoc: Get current ZMQ error descriptions
		returns: String
	End Rem
	Function Error:String()
		Return zmq_strerror( TZMQ.ErrorNumber() )
	End Function

	Rem
		bbdoc: Get current ZMQ error number
		returns: Int
	End Rem
	Function ErrorNumber:Int()
		Return zmq_errno()
	End Function
End Type


Wrapped another XMLRPC-EPI function, XMLRPC_GetVectoryType()

'XMLRPC_REQUEST_TYPE
Const xmlrpc_request_none:Int = 0
Const xmlrpc_request_call:Int = 1
Const xmlrpc_request_response:Int = 2

'XMLRPC_VERSION
Const xmlrpc_version_none:Int = 0
Const xmlrpc_version_1_0:Int = 1
Const xmlrpc_version_simple:Int = 2
Const xmlrpc_version_danda:Int = 2
Const xmlrpc_version_soap_1_1:Int = 3

'XMLRPC_VECTOR_TYPE
Const xmlrpc_vector_none:Int = 0
Const xmlrpc_vector_array:Int = 1
Const xmlrpc_vector_mixed:Int = 2
Const xmlrpc_vector_struct:Int = 3

'XMLRPC_VALUE_TYPE
Const xmlrpc_none:Int = 0
Const xmlrpc_empty:Int = 1
Const xmlrpc_base64:Int = 2
Const xmlrpc_boolean:Int = 3
Const xmlrpc_datetime:Int = 4
Const xmlrpc_double:Int = 5
Const xmlrpc_int:Int = 6
Const xmlrpc_string:Int = 7
Const xmlrpc_vector:Int = 8

'XMLRPC_VALUE_TYPE_EASY
Const xmlrpc_type_none:Int = 0
Const xmlrpc_type_empty:Int = 1
Const xmlrpc_type_base64:Int = 2
Const xmlrpc_type_boolean:Int = 3
Const xmlrpc_type_datetime:Int = 4
Const xmlrpc_type_double:Int = 5
Const xmlrpc_type_int:Int = 6
Const xmlrpc_type_string:Int = 7
Const xmlrpc_type_array:Int = 8
Const xmlrpc_type_mixed:Int = 9
Const xmlrpc_type_struct:Int = 10

'XMLRPC_ERROR_CODE
Const xmlrpc_error_none:Int = 0
Const xmlrpc_error_parse_xml_syntax:Int = -32700
Const xmlrpc_error_parse_unknown_encoding:Int = -32701
Const xmlrpc_error_parse_bad_encoding:Int = -32702
Const xmlrpc_error_invalid_xmlrpc:Int = -32600
Const xmlrpc_error_unknown_method:Int = -32601
Const xmlrpc_error_invalid_params:Int = -32602
Const xmlrpc_error_internal_server:Int = -32603
Const xmlrpc_error_application:Int = -32500
Const xmlrpc_error_system:Int = -32400
Const xmlrpc_error_transport:Int = -32300

'XMLRPC_SERVER_VALIDATION
Const validation_none:Int = 0
Const validation_all:Int = 1
Const validation_if_defined:Int = 2

'XMLRPC_CASE_COMPARISON
Const xmlrpc_case_insensitive:Int = 0
Const xmlrpc_case_sensitive:Int = 1

'XMLRPC_CASE
Const xmlrpc_case_exact:Int = 0
Const xmlrpc_case_lower:Int = 1
Const xmlrpc_case_upper:Int = 2

Extern
	Function XMLRPC_Create_STRUCT_XMLRPC_REQUEST_OUTPUT_OPTIONS:Byte Ptr(version:Int)
'	Function XMLRPC_Delete_Request_Output_Options(options:Byte Ptr)
End Extern

Extern "C"

	Function XMLRPC_RequestNew:Byte Ptr()
	Function XMLRPC_RequestFree(request:Byte Ptr, bFreeIO:Int)
	Function XMLRPC_RequestSetMethodName:Byte Ptr(request:Byte Ptr, methodName:Byte Ptr)
	Function XMLRPC_RequestGetMethodName:Byte Ptr(request:Byte Ptr)
	Function XMLRPC_RequestSetRequestType:Int(request:Byte Ptr, iType:Int)
	Function XMLRPC_RequestGetRequestType:Int(request:Byte Ptr)
	Function XMLRPC_RequestSetData:Byte Ptr(request:Byte Ptr, data:Byte Ptr)
	Function XMLRPC_RequestGetData:Byte Ptr(request:Byte Ptr)

	Function XMLRPC_REQUEST_ToXML:Byte Ptr(request:Byte Ptr, buf_len:Byte Ptr)
	Function XMLRPC_REQUEST_FromXML:Byte Ptr(in_buf:Byte Ptr, length:Int, in_options:Byte Ptr)
	Function XMLRPC_VALUE_ToXML:Byte Ptr(val:Int, buf_len:Byte Ptr)
	Function XMLRPC_VALUE_FromXML:Byte Ptr(in_buf:Byte Ptr, length:Int, in_options:Byte Ptr)

	Function XMLRPC_RequestSetOutputOptions:Byte Ptr(request:Byte Ptr, output:Byte Ptr)
	Function XMLRPC_RequestGetOutputOptions:Byte Ptr(request:Byte Ptr)

	Function XMLRPC_CreateVector:Byte Ptr(id:String, iType:Int)
	Function XMLRPC_AddValueToVector:Int(target:Byte Ptr, source:Byte Ptr)
	Function XMLRPC_VectorSize:Int(value:Byte Ptr)
	Function XMLRPC_VectorRewind:Byte Ptr(value:Byte Ptr)
	Function XMLRPC_VectorNext:Byte Ptr(value:Byte Ptr)

	Function XMLRPC_CreateValueBoolean:Byte Ptr(id:Byte Ptr, truth:Int)
	Function XMLRPC_CreateValueBase64:Byte Ptr(id:Byte Ptr, s:Byte Ptr, length:Int)
	Function XMLRPC_CreateValueDateTime:Byte Ptr(id:Byte Ptr, time:Long)
	Function XMLRPC_CreateValueDateTime_ISO8601:Byte Ptr(id:Byte Ptr, s:Byte Ptr)
	Function XMLRPC_CreateValueDouble:Byte Ptr(id:Byte Ptr, f:Double)
	Function XMLRPC_CreateValueInt:Byte Ptr(id:Byte Ptr, i:Int)
	Function XMLRPC_CreateValueEmpty:Byte Ptr()
	Function XMLRPC_CreateValueString:Byte Ptr(id:Byte Ptr, s:Byte Ptr, length:Int)

	Function XMLRPC_RequestSetError:Int(request:Byte Ptr, error:Int)
	Function XMLRPC_RequestGetError:Int(request:Byte Ptr)

	Function XMLRPC_VectorGetValueWithID_Case:Byte Ptr(vector:Byte Ptr, id:Byte Ptr, id_case:Int)

	Function XMLRPC_GetValueType:Int(v:Byte Ptr)
	'XMLRPC_VALUE_TYPE_EASY XMLRPC_GetValueTypeEasy(XMLRPC_VALUE v);
	Function XMLRPC_GetVectorType:Int(v:Byte Ptr)

	Function XMLRPC_GetValueString:Byte Ptr(value:Byte Ptr)
	Function XMLRPC_GetValueStringLen:Int(value:Byte Ptr)
	Function XMLRPC_GetValueInt:Int(value:Byte Ptr)
	Function XMLRPC_GetValueBoolean:Int(value:Byte Ptr)
	Function XMLRPC_GetValueDouble:Double(value:Byte Ptr)
	Function XMLRPC_GetValueBase64:Byte Ptr(value:Byte Ptr)
	Function XMLRPC_GetValueDateTime:Long(value:Byte Ptr)
	Function XMLRPC_GetValueDateTime_ISO8601:Byte Ptr(value:Byte Ptr)
	Function XMLRPC_GetValueID:Byte Ptr(value:Byte Ptr)

	Function XMLRPC_ValueIsFault:Int(value:Byte Ptr)
	Function XMLRPC_ResponseIsFault(response:Byte Ptr)
	Function XMLRPC_GetValueFaultCode:Int(value:Byte Ptr)
	Function XMLRPC_GetResponseFaultCode:Int(response:Byte Ptr)
	Function XMLRPC_GetValueFaultString:Byte Ptr(value:Byte Ptr)
	Function XMLRPC_GetResponseFaultString:Byte Ptr(response:Byte Ptr)
	Function XMLRPC_Free(mem:Byte Ptr)
	Function XMLRPC_GetVersionString:Byte Ptr()
Rem

XMLRPC_VALUE XMLRPC_UtilityCreateFault(int fault_code, const char* fault_string);

/* Get Values */
#define XMLRPC_VectorGetStringWithID(vector, id) XMLRPC_GetValueString(XMLRPC_VectorGetValueWithID(vector, id))
#define XMLRPC_VectorGetBase64WithID(vector, id) XMLRPC_GetValueBase64(XMLRPC_VectorGetValueWithID(vector, id))
#define XMLRPC_VectorGetDateTimeWithID(vector, id) XMLRPC_GetValueDateTime(XMLRPC_VectorGetValueWithID(vector, id))
#define XMLRPC_VectorGetDoubleWithID(vector, id) XMLRPC_GetValueDouble(XMLRPC_VectorGetValueWithID(vector, id))
#define XMLRPC_VectorGetIntWithID(vector, id) XMLRPC_GetValueInt(XMLRPC_VectorGetValueWithID(vector, id))
#define XMLRPC_VectorGetBooleanWithID(vector, id) XMLRPC_GetValueBoolean(XMLRPC_VectorGetValueWithID(vector, id))
Endrem
End Extern

Function XMLRPC_VectorGetValueWithID:Byte Ptr(vector:Byte Ptr, id:String)
	Return XMLRPC_VectorGetValueWithID_Case(vector, id.ToCString(), xmlrpc_case_sensitive)
End Function

Function XMLRPC_VectorAppendString:Int(vector:Byte Ptr, id:String, s:String, length:Int = 0)
	Return XMLRPC_AddValueToVector(vector, XMLRPC_CreateValueString(id.ToCString(), s.ToCString(), length))
End Function

Function XMLRPC_VectorAppendBase64:Int(vector:Byte Ptr, id:String, s:String, length:Int = 0)
	Return XMLRPC_AddValueToVector(vector, XMLRPC_CreateValueBase64(id.ToCString(), s.ToCString(), length))
End Function

Function XMLRPC_VectorAppendDateTime:Int(vector:Byte Ptr, id:Byte Ptr, time:Long)
	Return XMLRPC_AddValueToVector(vector, XMLRPC_CreateValueDateTime(id, time))
End Function

Function XMLRPC_VectorAppendDateTime_ISO8601:Int(vector:Byte Ptr, id:String, s:String)
	Return XMLRPC_AddValueToVector(vector, XMLRPC_CreateValueDateTime_ISO8601(id.ToCString(), s.ToCString()))
End Function

Function XMLRPC_VectorAppendDouble:Int(vector:Byte Ptr, id:String, f:Double)
	Return XMLRPC_AddValueToVector(vector, XMLRPC_CreateValueDouble(id.ToCString(), f))
End Function

Function XMLRPC_VectorAppendInt:Int(vector:Byte Ptr, id:String, i:Int)
	Return XMLRPC_AddValueToVector(vector, XMLRPC_CreateValueInt(id.ToCString(), i))
End Function

Function XMLRPC_VectorAppendBoolean:Int(vector:Byte Ptr, id:String, i:Int)
	Return XMLRPC_AddValueToVector(vector, XMLRPC_CreateValueBoolean(id.ToCString(), i))
End Function
rem
/****s* VALUE/XMLRPC_REQUEST_INPUT_OPTIONS
 * NAME
 *   XMLRPC_REQUEST_INPUT_OPTIONS
 * NOTES
 *   Defines options for reading in xml data
 * SEE ALSO
 *   XMLRPC_VERSION
 *   XML_ELEM_INPUT_OPTIONS
 *   XMLRPC_REQUEST_From_XML ()
 * SOURCE
 */
typedef struct _xmlrpc_request_input_options {
   STRUCT_XML_ELEM_INPUT_OPTIONS  xml_elem_opts;  /* xml_element specific output options */
} STRUCT_XMLRPC_REQUEST_INPUT_OPTIONS, *XMLRPC_REQUEST_INPUT_OPTIONS;
/******/

/****s* VALUE/XMLRPC_ERROR
 * NAME
 *   XMLRPC_ERROR
 * NOTES
 *   For the reporting and handling of errors
 * SOURCE
 */
typedef struct _xmlrpc_error {
   XMLRPC_ERROR_CODE      code;
   STRUCT_XML_ELEM_ERROR  xml_elem_error;  /* xml_element errors (parser errors) */
} STRUCT_XMLRPC_ERROR, *XMLRPC_ERROR;
/******/
endrem

Rem
	String conversion stuff below. Shamelessly stolen from Brucey :-)
End Rem

Extern
	Function _strlen:Int(s:Byte Ptr) = "strlen"
End Extern

' Convert from Max to UTF8
Function convertISO8859toUTF8:String(text:String)
	If Not text Then
		Return ""
	End If

	Local l:Int = text.length
	If l = 0 Then
		Return ""
	End If

	Local count:Int = 0
	Local s:Byte[] = New Byte[l * 3]

	For Local i:Int = 0 Until l
		Local char:Int = text[i]

		If char < 128 Then
			s[count] = char
			count:+ 1
			Continue
		Else If char<2048
			s[count] = char/64 | 192
			count:+ 1
			s[count] = char Mod 64 | 128
			count:+ 1
			Continue
		Else
			s[count] =  char/4096 | 224
			count:+ 1
			s[count] = char/64 Mod 64 | 128
			count:+ 1
			s[count] = char Mod 64 | 128
			count:+ 1
			Continue
		EndIf

	Next

	Return String.fromBytes(s, count)
End Function

' Convert from UTF8 to Max
Function convertUTF8toISO8859:String(s:Byte Ptr)

	Local l:Int = _strlen(s)

	Local b:Short[] = New Short[l]
	Local bc:Int = -1
	Local c:Int
	Local d:Int
	Local e:Int
	For Local i:Int = 0 Until l

		bc:+1
		c = s[i]
		If c<128
			b[bc] = c
			Continue
		End If
		i:+1
		d=s[i]
		If c<224
			b[bc] = (c-192)*64+(d-128)
			Continue
		End If
		i:+1
		e = s[i]
		If c < 240
			b[bc] = (c-224)*4096+(d-128)*64+(e-128)
			If b[bc] = 8233 Then
				b[bc] = 10
			End If
			Continue
		End If
	Next

	Return String.fromshorts(b, bc + 1)
End Function

Function sizedUTF8toISO8859:String(s:Byte Ptr, size:Int)

	Local l:Int = size
	Local b:Short[] = New Short[l]
	Local bc:Int = -1
	Local c:Int
	Local d:Int
	Local e:Int
	For Local i:Int = 0 Until l

		c = s[i]
		If c = 0 Continue

		bc:+1
		If c<128
			b[bc] = c
			Continue
		End If
		i:+1
		d=s[i]
		If c<224
			b[bc] = (c-192)*64+(d-128)
			Continue
		End If
		i:+1
		e = s[i]
		If c < 240
			b[bc] = (c-224)*4096+(d-128)*64+(e-128)
			If b[bc] = 8233 Then
				b[bc] = 10
			End If
			Continue
		End If
	Next

	Return String.fromshorts(b, bc + 1)
End Function

'XMLRPC_REQUEST_TYPE
Const xmlrpc_request_none:Int = 0
Const xmlrpc_request_call:Int = 1
Const xmlrpc_request_response:Int = 2

'XMLRPC_VERSION
Const xmlrpc_version_none:Int = 0
Const xmlrpc_version_1_0:Int = 1
Const xmlrpc_version_simple:Int = 2
Const xmlrpc_version_danda:Int = 2
Const xmlrpc_version_soap_1_1:Int = 3

'XMLRPC_VECTOR_TYPE
Const xmlrpc_vector_none:Int = 0
Const xmlrpc_vector_array:Int = 1
Const xmlrpc_vector_mixed:Int = 2
Const xmlrpc_vector_struct:Int = 3

'XMLRPC_VALUE_TYPE
Const xmlrpc_none:Int = 0
Const xmlrpc_empty:Int = 1
Const xmlrpc_base64:Int = 2
Const xmlrpc_boolean:Int = 3
Const xmlrpc_datetime:Int = 4
Const xmlrpc_double:Int = 5
Const xmlrpc_int:Int = 6
Const xmlrpc_string:Int = 7
Const xmlrpc_vector:Int = 8

'XMLRPC_VALUE_TYPE_EASY
Const xmlrpc_type_none:Int = 0
Const xmlrpc_type_empty:Int = 1
Const xmlrpc_type_base64:Int = 2
Const xmlrpc_type_boolean:Int = 3
Const xmlrpc_type_datetime:Int = 4
Const xmlrpc_type_double:Int = 5
Const xmlrpc_type_int:Int = 6
Const xmlrpc_type_string:Int = 7
Const xmlrpc_type_array:Int = 8
Const xmlrpc_type_mixed:Int = 9
Const xmlrpc_type_struct:Int = 10

'XMLRPC_ERROR_CODE
Const xmlrpc_error_none:Int = 0
Const xmlrpc_error_parse_xml_syntax:Int = -32700
Const xmlrpc_error_parse_unknown_encoding:Int = -32701
Const xmlrpc_error_parse_bad_encoding:Int = -32702
Const xmlrpc_error_invalid_xmlrpc:Int = -32600
Const xmlrpc_error_unknown_method:Int = -32601
Const xmlrpc_error_invalid_params:Int = -32602
Const xmlrpc_error_internal_server:Int = -32603
Const xmlrpc_error_application:Int = -32500
Const xmlrpc_error_system:Int = -32400
Const xmlrpc_error_transport:Int = -32300

'XMLRPC_SERVER_VALIDATION
Const validation_none:Int = 0
Const validation_all:Int = 1
Const validation_if_defined:Int = 2

'XMLRPC_CASE_COMPARISON
Const xmlrpc_case_insensitive:Int = 0
Const xmlrpc_case_sensitive:Int = 1

'XMLRPC_CASE
Const xmlrpc_case_exact:Int = 0
Const xmlrpc_case_lower:Int = 1
Const xmlrpc_case_upper:Int = 2

Extern
	Function XMLRPC_Create_STRUCT_XMLRPC_REQUEST_OUTPUT_OPTIONS:Byte Ptr(version:Int)
'	Function XMLRPC_Delete_Request_Output_Options(options:Byte Ptr)
End Extern

Extern "C"

	Function XMLRPC_RequestNew:Byte Ptr()
	Function XMLRPC_RequestFree(request:Byte Ptr, bFreeIO:Int)
	Function XMLRPC_RequestSetMethodName:Byte Ptr(request:Byte Ptr, methodName:Byte Ptr)
	Function XMLRPC_RequestGetMethodName:Byte Ptr(request:Byte Ptr)
	Function XMLRPC_RequestSetRequestType:Int(request:Byte Ptr, iType:Int)
	Function XMLRPC_RequestGetRequestType:Int(request:Byte Ptr)
	Function XMLRPC_RequestSetData:Byte Ptr(request:Byte Ptr, data:Byte Ptr)
	Function XMLRPC_RequestGetData:Byte Ptr(request:Byte Ptr)

	Function XMLRPC_REQUEST_ToXML:Byte Ptr(request:Byte Ptr, buf_len:Byte Ptr)
	Function XMLRPC_REQUEST_FromXML:Byte Ptr(in_buf:Byte Ptr, length:Int, in_options:Byte Ptr)
	Function XMLRPC_VALUE_ToXML:Byte Ptr(val:Int, buf_len:Byte Ptr)
	Function XMLRPC_VALUE_FromXML:Byte Ptr(in_buf:Byte Ptr, length:Int, in_options:Byte Ptr)

	Function XMLRPC_RequestSetOutputOptions:Byte Ptr(request:Byte Ptr, output:Byte Ptr)
	Function XMLRPC_RequestGetOutputOptions:Byte Ptr(request:Byte Ptr)

	Function XMLRPC_CreateVector:Byte Ptr(id:String, iType:Int)
	Function XMLRPC_AddValueToVector:Int(target:Byte Ptr, source:Byte Ptr)
	Function XMLRPC_VectorSize:Int(value:Byte Ptr)
	Function XMLRPC_VectorRewind:Byte Ptr(value:Byte Ptr)
	Function XMLRPC_VectorNext:Byte Ptr(value:Byte Ptr)

	Function XMLRPC_CreateValueBoolean:Byte Ptr(id:Byte Ptr, truth:Int)
	Function XMLRPC_CreateValueBase64:Byte Ptr(id:Byte Ptr, s:Byte Ptr, length:Int)
	Function XMLRPC_CreateValueDateTime:Byte Ptr(id:Byte Ptr, time:Long)
	Function XMLRPC_CreateValueDateTime_ISO8601:Byte Ptr(id:Byte Ptr, s:Byte Ptr)
	Function XMLRPC_CreateValueDouble:Byte Ptr(id:Byte Ptr, f:Double)
	Function XMLRPC_CreateValueInt:Byte Ptr(id:Byte Ptr, i:Int)
	Function XMLRPC_CreateValueEmpty:Byte Ptr()
	Function XMLRPC_CreateValueString:Byte Ptr(id:Byte Ptr, s:Byte Ptr, length:Int)

	Function XMLRPC_RequestSetError:Int(request:Byte Ptr, error:Int)
	Function XMLRPC_RequestGetError:Int(request:Byte Ptr)

	Function XMLRPC_VectorGetValueWithID_Case:Byte Ptr(vector:Byte Ptr, id:Byte Ptr, id_case:Int)

	Function XMLRPC_GetValueType:Int(v:Byte Ptr)
	'XMLRPC_VALUE_TYPE_EASY XMLRPC_GetValueTypeEasy(XMLRPC_VALUE v);
	'XMLRPC_VECTOR_TYPE XMLRPC_GetVectorType(XMLRPC_VALUE v);

	Function XMLRPC_GetValueString:Byte Ptr(value:Byte Ptr)
	Function XMLRPC_GetValueStringLen:Int(value:Byte Ptr)
	Function XMLRPC_GetValueInt:Int(value:Byte Ptr)
	Function XMLRPC_GetValueBoolean:Int(value:Byte Ptr)
	Function XMLRPC_GetValueDouble:Double(value:Byte Ptr)
	Function XMLRPC_GetValueBase64:Byte Ptr(value:Byte Ptr)
	Function XMLRPC_GetValueDateTime:Long(value:Byte Ptr)
	Function XMLRPC_GetValueDateTime_ISO8601:Byte Ptr(value:Byte Ptr)
	Function XMLRPC_GetValueID:Byte Ptr(value:Byte Ptr)

	Function XMLRPC_ValueIsFault:Int(value:Byte Ptr)
	Function XMLRPC_ResponseIsFault(response:Byte Ptr)
	Function XMLRPC_GetValueFaultCode:Int(value:Byte Ptr)
	Function XMLRPC_GetResponseFaultCode:Int(response:Byte Ptr)
	Function XMLRPC_GetValueFaultString:Byte Ptr(value:Byte Ptr)
	Function XMLRPC_GetResponseFaultString:Byte Ptr(response:Byte Ptr)
	Function XMLRPC_Free(mem:Byte Ptr)
	Function XMLRPC_GetVersionString:Byte Ptr()
Rem

XMLRPC_VALUE XMLRPC_UtilityCreateFault(int fault_code, const char* fault_string);

/* Get Values */
#define XMLRPC_VectorGetStringWithID(vector, id) XMLRPC_GetValueString(XMLRPC_VectorGetValueWithID(vector, id))
#define XMLRPC_VectorGetBase64WithID(vector, id) XMLRPC_GetValueBase64(XMLRPC_VectorGetValueWithID(vector, id))
#define XMLRPC_VectorGetDateTimeWithID(vector, id) XMLRPC_GetValueDateTime(XMLRPC_VectorGetValueWithID(vector, id))
#define XMLRPC_VectorGetDoubleWithID(vector, id) XMLRPC_GetValueDouble(XMLRPC_VectorGetValueWithID(vector, id))
#define XMLRPC_VectorGetIntWithID(vector, id) XMLRPC_GetValueInt(XMLRPC_VectorGetValueWithID(vector, id))
#define XMLRPC_VectorGetBooleanWithID(vector, id) XMLRPC_GetValueBoolean(XMLRPC_VectorGetValueWithID(vector, id))
Endrem
End Extern

Function XMLRPC_VectorGetValueWithID:Byte Ptr(vector:Byte Ptr, id:String)
	Return XMLRPC_VectorGetValueWithID_Case(vector, id.ToCString(), xmlrpc_case_sensitive)
End Function

Function XMLRPC_VectorAppendString:Int(vector:Byte Ptr, id:String, s:String, length:Int = 0)
	Return XMLRPC_AddValueToVector(vector, XMLRPC_CreateValueString(id.ToCString(), s.ToCString(), length))
End Function

Function XMLRPC_VectorAppendBase64:Int(vector:Byte Ptr, id:String, s:String, length:Int = 0)
	Return XMLRPC_AddValueToVector(vector, XMLRPC_CreateValueBase64(id.ToCString(), s.ToCString(), length))
End Function

Function XMLRPC_VectorAppendDateTime:Int(vector:Byte Ptr, id:Byte Ptr, time:Long)
	Return XMLRPC_AddValueToVector(vector, XMLRPC_CreateValueDateTime(id, time))
End Function

Function XMLRPC_VectorAppendDateTime_ISO8601:Int(vector:Byte Ptr, id:String, s:String)
	Return XMLRPC_AddValueToVector(vector, XMLRPC_CreateValueDateTime_ISO8601(id.ToCString(), s.ToCString()))
End Function

Function XMLRPC_VectorAppendDouble:Int(vector:Byte Ptr, id:String, f:Double)
	Return XMLRPC_AddValueToVector(vector, XMLRPC_CreateValueDouble(id.ToCString(), f))
End Function

Function XMLRPC_VectorAppendInt:Int(vector:Byte Ptr, id:String, i:Int)
	Return XMLRPC_AddValueToVector(vector, XMLRPC_CreateValueInt(id.ToCString(), i))
End Function

Function XMLRPC_VectorAppendBoolean:Int(vector:Byte Ptr, id:String, i:Int)
	Return XMLRPC_AddValueToVector(vector, XMLRPC_CreateValueBoolean(id.ToCString(), i))
End Function
rem
/****s* VALUE/XMLRPC_REQUEST_INPUT_OPTIONS
 * NAME
 *   XMLRPC_REQUEST_INPUT_OPTIONS
 * NOTES
 *   Defines options for reading in xml data
 * SEE ALSO
 *   XMLRPC_VERSION
 *   XML_ELEM_INPUT_OPTIONS
 *   XMLRPC_REQUEST_From_XML ()
 * SOURCE
 */
typedef struct _xmlrpc_request_input_options {
   STRUCT_XML_ELEM_INPUT_OPTIONS  xml_elem_opts;  /* xml_element specific output options */
} STRUCT_XMLRPC_REQUEST_INPUT_OPTIONS, *XMLRPC_REQUEST_INPUT_OPTIONS;
/******/

/****s* VALUE/XMLRPC_ERROR
 * NAME
 *   XMLRPC_ERROR
 * NOTES
 *   For the reporting and handling of errors
 * SOURCE
 */
typedef struct _xmlrpc_error {
   XMLRPC_ERROR_CODE      code;
   STRUCT_XML_ELEM_ERROR  xml_elem_error;  /* xml_element errors (parser errors) */
} STRUCT_XMLRPC_ERROR, *XMLRPC_ERROR;
/******/
endrem

Rem
	String conversion stuff below. Shamelessly stolen from Brucey :-)
End Rem

Extern
	Function _strlen:Int(s:Byte Ptr) = "strlen"
End Extern

' Convert from Max to UTF8
Function convertISO8859toUTF8:String(text:String)
	If Not text Then
		Return ""
	End If

	Local l:Int = text.length
	If l = 0 Then
		Return ""
	End If

	Local count:Int = 0
	Local s:Byte[] = New Byte[l * 3]

	For Local i:Int = 0 Until l
		Local char:Int = text[i]

		If char < 128 Then
			s[count] = char
			count:+ 1
			Continue
		Else If char<2048
			s[count] = char/64 | 192
			count:+ 1
			s[count] = char Mod 64 | 128
			count:+ 1
			Continue
		Else
			s[count] =  char/4096 | 224
			count:+ 1
			s[count] = char/64 Mod 64 | 128
			count:+ 1
			s[count] = char Mod 64 | 128
			count:+ 1
			Continue
		EndIf

	Next

	Return String.fromBytes(s, count)
End Function

' Convert from UTF8 to Max
Function convertUTF8toISO8859:String(s:Byte Ptr)

	Local l:Int = _strlen(s)

	Local b:Short[] = New Short[l]
	Local bc:Int = -1
	Local c:Int
	Local d:Int
	Local e:Int
	For Local i:Int = 0 Until l

		bc:+1
		c = s[i]
		If c<128
			b[bc] = c
			Continue
		End If
		i:+1
		d=s[i]
		If c<224
			b[bc] = (c-192)*64+(d-128)
			Continue
		End If
		i:+1
		e = s[i]
		If c < 240
			b[bc] = (c-224)*4096+(d-128)*64+(e-128)
			If b[bc] = 8233 Then
				b[bc] = 10
			End If
			Continue
		End If
	Next

	Return String.fromshorts(b, bc + 1)
End Function

Function sizedUTF8toISO8859:String(s:Byte Ptr, size:Int)

	Local l:Int = size
	Local b:Short[] = New Short[l]
	Local bc:Int = -1
	Local c:Int
	Local d:Int
	Local e:Int
	For Local i:Int = 0 Until l

		c = s[i]
		If c = 0 Continue

		bc:+1
		If c<128
			b[bc] = c
			Continue
		End If
		i:+1
		d=s[i]
		If c<224
			b[bc] = (c-192)*64+(d-128)
			Continue
		End If
		i:+1
		e = s[i]
		If c < 240
			b[bc] = (c-224)*4096+(d-128)*64+(e-128)
			If b[bc] = 8233 Then
				b[bc] = 10
			End If
			Continue
		End If
	Next

	Return String.fromshorts(b, bc + 1)
End Function


ipc protocol not supported on Windows

ipc protocol not supported on Windows

Rem
	Weather Update server
	Binds PUB socket to tcp://127.0.0.1:6000
	Publishes random weather updates
End Rem
SuperStrict
Import htbaapub.zmq

Local context:TZMQ_Context = New TZMQ_Context.Create(1)
Local publisher:TZMQ_Socket = context.Socket(ZMQ_PUB)
publisher.Bind("tcp://127.0.0.1:6000")
?Not Win32
publisher.Bind("ipc://weather.ipc")
?

Repeat
	Local zipcode:Int = Rand(1, 100000)
	Local temperature:Int = Rand(1,215) - 80
	Local relhumidity:Int = Rand(1,50) + 10
	Local update:String = " ".Join([String(zipcode), String(temperature), String(relhumidity)])
	publisher.Send(update)
Forever

Rem
	Weather Update server
	Binds PUB socket to tcp://127.0.0.1:6000
	Publishes random weather updates
End Rem
SuperStrict
Import htbaapub.zmq

Local context:TZMQ_Context = New TZMQ_Context.Create(1)
Local publisher:TZMQ_Socket = context.Socket(ZMQ_PUB)
publisher.Bind("tcp://127.0.0.1:6000")
publisher.Bind("ipc://weather.ipc")

Repeat
	Local zipcode:Int = Rand(1, 100000)
	Local temperature:Int = Rand(1,215) - 80
	Local relhumidity:Int = Rand(1,50) + 10
	Local update:String = " ".Join([String(zipcode), String(temperature), String(relhumidity)])
	publisher.Send(update)
Forever


Changed Content-type in TXMLRPC_Transport_Http from application/x-www-form-urlencoded to application/octet-stream as some XML-RPC servers can be picky about it. Newline (\r\n) is now done in a more proper way as well.

Rem
	bbdoc: Transport interface. Use this as a blueprint for any other transport layers
End Rem
Type TXMLRPC_Transport_Interface Abstract
	Rem
		bbdoc: Send request to XML-RPC server
	End Rem
	Method DoRequest:String(message:String) Abstract
End Type

Rem
	bbdoc: A Dummy transport type. Do not use, deprecated
End Rem
Type TXMLRPC_Transport_Dummy Extends TXMLRPC_Transport_Interface
	Method DoRequest:String(message:String)
		Return message
	End Method
End Type

Rem
	bbdoc: Exception for TXMLRPC_Transport_Http
End Rem
Type TXMLRPC_Transport_Http_Exception Extends TXMLRPC_Exception
End Type

Rem
	bbdoc: Simple HTTP transport
	about: Needs HTTP error handling. Implementation is very simple at the moment. Only a simple HTTP request is possible. Authenticated pages or HTTPS isn't supported. For HTTPS, write your own.
End Rem
Type TXMLRPC_Transport_Http Extends TXMLRPC_Transport_Interface
	Field host:String
	Field path:String
	Field port:Int

	Rem
		bbdoc: Set useragent for HTTP request.
		about: This string will be used to identify the client with the XML-RPC server
	End rem
	Field userAgent:String = "htbaapub.mod/xmlrpc.mod"

	Rem
		bbdoc: Create TXMLRPC_Transport_Http object
	End Rem
	Method Create:TXMLRPC_Transport_Http(host:String, path:String, port:Int = 80)
		Self.host = host
		Self.path = path
		Self.port = port
		Return Self
	End Method

	Rem
		bbdoc: Send request over HTTP
	End Rem
	Method DoRequest:String(message:String)
		Local socket:TSocket = CreateTCPSocket()

		If Not ConnectSocket(socket, HostIp(Self.host), Self.port)
			Throw New TXMLRPC_Transport_Http_Exception.Create("Couldn't open socket to " + Self.host)
		End If

		Local stream:TSocketStream = CreateSocketStream(socket)

		WriteLine(stream, "POST " + Self.path + " HTTP/1.0")
		WriteLine(stream, "Accept: */*")
		WriteLine(stream, "Host: " + Self.host)
		WriteLine(stream, "Content-type: application/octet-stream")
		WriteLine(stream, "User-agent: " + Self.userAgent)
		WriteLine(stream, "Pragma: no-cache")
		WriteLine(stream, "Content-length: " + message.Length)
		WriteLine(stream, "Connection: keep-alive")
		WriteLine(stream, "")
		WriteLine(stream, message)

		FlushStream(stream)

		Local buffer:String
		While Not Eof(stream)
			Local line:String = ReadLine(stream)
			buffer:+line + "~n"
		Wend

		CloseSocket(socket)
		Return buffer
	End Method
End Type

Rem
	bbdoc: Transport interface. Use this as a blueprint for any other transport layers
End Rem
Type TXMLRPC_Transport_Interface Abstract
	Rem
		bbdoc: Send request to XML-RPC server
	End Rem
	Method DoRequest:String(message:String) Abstract
End Type

Rem
	bbdoc: A Dummy transport type. Do not use, deprecated
End Rem
Type TXMLRPC_Transport_Dummy Extends TXMLRPC_Transport_Interface
	Method DoRequest:String(message:String)
		Return message
	End Method
End Type

Rem
	bbdoc: Exception for TXMLRPC_Transport_Http
End Rem
Type TXMLRPC_Transport_Http_Exception Extends TXMLRPC_Exception
End Type

Rem
	bbdoc: Simple HTTP transport
	about: Needs HTTP error handling. Implementation is very simple at the moment. Only a simple HTTP request is possible. Authenticated pages or HTTPS isn't supported. For HTTPS, write your own.
End Rem
Type TXMLRPC_Transport_Http Extends TXMLRPC_Transport_Interface
	Field host:String
	Field path:String
	Field port:Int

	Rem
		bbdoc: Set useragent for HTTP request.
		about: This string will be used to identify the client with the XML-RPC server
	End rem
	Field userAgent:String = "htbaapub.mod/xmlrpc.mod"

	Rem
		bbdoc: Create TXMLRPC_Transport_Http object
	End Rem
	Method Create:TXMLRPC_Transport_Http(host:String, path:String, port:Int = 80)
		Self.host = host
		Self.path = path
		Self.port = port
		Return Self
	End Method

	Rem
		bbdoc: Send request over HTTP
	End Rem
	Method DoRequest:String(message:String)
		Local socket:TSocket = CreateTCPSocket()

		If Not ConnectSocket(socket, HostIp(Self.host), Self.port)
			Throw New TXMLRPC_Transport_Http_Exception.Create("Couldn't open socket to " + Self.host)
		End If

		Local stream:TSocketStream = CreateSocketStream(socket)

		WriteLine(stream, "POST " + Self.path + " HTTP/1.0")
		WriteLine(stream, "Accept: */*")
		WriteLine(stream, "Host: " + Self.host)
		WriteLine(stream, "Content-type: application/x-www-form-urlencoded")
		WriteLine(stream, "User-agent: " + Self.userAgent)
		WriteLine(stream, "Pragma: no-cache")
		WriteLine(stream, "Connection: keep-alive")
		WriteLine(stream, "Content-length: " + message.Length + "~n")

		WriteLine(stream, message + "~n~n")

		FlushStream(stream)

		Local buffer:String
		While Not Eof(stream)
			Local line:String = ReadLine(stream)
			buffer:+line + "~n"
		Wend

		CloseSocket(socket)
		Return buffer
	End Method
End Type