Incremented version number to 0.9b.  Added LUGI_VERSION_* integers to compare again (e.g., for things like Assert LUGI_VERSION_NUMBER >= LUGI_VERSION_0_9_0B Else "Must use LuGI 0.9b or later") - these don't work in 0.8.02, obviously, but I'm willing to accept that.

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.9b"
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

' Version information
Const LUGI_VERSION_0_8_2:Int = 00800200
Const LUGI_VERSION_0_9_0B:Int = 00900002
Const LUGI_VERSION_NUMBER:Int = 00900002 ' MMmmrrxx where xxx will be any arbitrary digits I assign.
Const LUGI_VERSION:String = "0.9b"

' 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

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


[argparser] (v0.3) 	Fixed argument parsing for commands

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

SuperStrict

Rem
bbdoc: Application argument parser
End Rem
Module duct.argparser

ModuleInfo "Version: 0.3"
ModuleInfo "Copyright: Tim Howard"
ModuleInfo "License: MIT"

ModuleInfo "History: Version 0.3"
ModuleInfo "History: Fixed argument parsing for commands"
ModuleInfo "History: Version 0.2"
ModuleInfo "History: Updated for API change"
ModuleInfo "History: Version 0.1"
ModuleInfo "History: Initial version."

Import duct.variables

Rem
	bbdoc: duct argument parser.
End Rem
Type dArgParser

	Rem
		bbdoc: Parse the given arguments string array.
		returns: The root identifier containing the parsed arguments.
		about: The expected value does not (by default) contain the first argument of AppArgs (the application location).

		You can override this by passing @fullargs as True. If you leave it as False, the root identifier's name will be set to the first argument (which should be the application location - if you're passing AppArgs).

		@optarglimit limits how many arguments can be given to an option (options start with "--" or "-"). If set to -1, there is no limit.
	End Rem
	Function ParseArray:dIdentifier(args:String[], fullargs:Int = False, optarglimit:Int = 1)
		Local root:dIdentifier = New dIdentifier.Create()
		If fullargs = True
			root.SetName(args[0])
			args = args[1..]
		End If
		Local arg:String, i:Int, length:Int = args.Length - 1
		Local sub:dIdentifier, subset:Int = False
		optarglimit = (optarglimit = -1) And length Or optarglimit
		For i = 0 To length
			arg = args[i]
			sub = New dIdentifier.CreateByData(arg, Null)
			If arg <> Null And arg[0] = 45 ' "-"
				Local lim:Int = Min(length, i + optarglimit)
				i:+1
				While i <= length
					arg = args[i]
					If arg = Null Or arg[0] <> 45 ' "-"
						sub.AddValue(dVariable.RawToVariable(arg))
						i:+1
						If i > lim
							i:-1
							Exit
						End If
					Else
						i:-1
						Exit
					End If
				End While
				root.AddValue(sub)
			Else
				If subset = False
					root.AddValue(sub)
					root = sub
					subset = True
				Else
					root.AddValue(dVariable.RawToVariable(arg))
				End If
			End If
		Next
		While Not root.GetParent() = Null
			root = dIdentifier(root.GetParent())
		End While
		Return root
	End Function

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

SuperStrict

Rem
bbdoc: Application argument parser
End Rem
Module duct.argparser

ModuleInfo "Version: 0.2"
ModuleInfo "Copyright: Tim Howard"
ModuleInfo "License: MIT"

ModuleInfo "History: Version 0.2"
ModuleInfo "History: Updated for API change"
ModuleInfo "History: Version 0.1"
ModuleInfo "History: Initial version."

Import duct.variables

Rem
	bbdoc: duct argument parser.
End Rem
Type dArgParser

	Rem
		bbdoc: Parse the given arguments string array.
		returns: The root identifier containing the parsed arguments.
		about: The expected value does not (by default) contain the first argument of AppArgs (the application location).

		You can override this by passing @fullargs as True. If you leave it as False, the root identifier's name will be set to the first argument (which should be the application location - if you're passing AppArgs).

		@optarglimit limits how many arguments can be given to an option (options start with "--" or "-"). If set to -1, there is no limit.
	End Rem
	Function ParseArray:dIdentifier(args:String[], fullargs:Int = False, optarglimit:Int = 1)
		Local root:dIdentifier = New dIdentifier.Create()
		If fullargs = True
			root.SetName(args[0])
			args = args[1..]
		End If
		Local arg:String, i:Int, length:Int = args.Length - 1
		Local sub:dIdentifier, subset:Int = False
		optarglimit = (optarglimit = -1) And length Or optarglimit
		For i = 0 To length
			arg = args[i]
			sub = New dIdentifier.CreateByData(arg, Null)
			If arg <> Null And arg[0] = 45 ' "-"
				Local lim:Int = Min(length, i + optarglimit)
				i:+1
				While i <= length
					arg = args[i]
					If arg = Null Or arg[0] <> 45 ' "-"
						sub.AddValue(dVariable.RawToVariable(arg))
						i:+1
						If i > lim
							i:-1
							Exit
						End If
					Else
						i:-1
						Exit
					End If
				End While
				root.AddValue(sub)
			Else
				root.AddValue(sub)
				If subset = False
					root = sub
					subset = True
				End If
			End If
		Next
		While Not root.GetParent() = Null
			root = dIdentifier(root.GetParent())
		End While
		Return root
	End Function

End Type


Parameter name correction :|

Parameter name correction :|

Rem
	Copyright (c) 2009 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.
	-----------------------------------------------------------------------------

	server.bmx (Contains: TServer, )

End Rem

Rem
	bbdoc: duct generic server type.
End Rem
Type TServer

	Field m_socket:TSocket
	Field m_port:Int, m_accept_timeout:Int

	Field m_msgmap:TNetMessageMap
	Field m_clients:TListEx

	Method New()
		m_clients = New TListEx
	End Method

	Method _init(msgmap:TNetMessageMap, socket:TSocket, port:Int, accept_timeout:Int)
		SetMessageMap(msgmap)
		SetSocket(socket)
		SetPort(port)
		SetAcceptTimeout(accept_timeout)
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the timeout when the Server attempts to accept a Client.
		returns: Nothing.
	End Rem
	Method SetAcceptTimeout(accept_timeout:Int)
		m_accept_timeout = accept_timeout
	End Method

	Rem
		bbdoc: Get the timout (ms) used when the Server attempts to accept a Client.
		returns: The accept timeout for this Server.
	End Rem
	Method GetAcceptTimeout:Int()
		Return m_accept_timeout
	End Method

	Rem
		bbdoc: Set the Server's port.
		returns: Nothing.
	End Rem
	Method SetPort(port:Int)
		m_port = port
	End Method

	Rem
		bbdoc: Get the Server's port.
		returns: The port for this Server.
	End Rem
	Method GetPort:Int()
		Return m_port
	End Method

	Rem
		bbdoc: Set the socket for the Server.
		returns: Nothing.
		about: This will close all existing client connections.
	End Rem
	Method SetSocket(socket:TSocket)
		Close()
		m_socket = socket
	End Method

	Rem
		bbdoc: Get the Socket for the Server.
		returns: The socket for this Server.
	End Rem
	Method GetSocket:TSocket()
		Return m_socket
	End Method

	Rem
		bbdoc: Set the MessageMap for the Server.
		returns: Nothing.
	End Rem
	Method SetMessageMap(msgmap:TNetMessageMap)
		m_msgmap = msgmap
	End Method

	Rem
		bbdoc: Get the MessageMap for the Server.
		returns: The MessageMap for this server.
	End Rem
	Method GetMessageMap:TNetMessageMap()
		Return m_msgmap
	End Method

'#end region (Field accessors)

'#region Connection

	Rem
		bbdoc: Start the server connection.
		returns: True if the socket was able to open on the Server's port, or False if it was unable.
	End Rem
	Method Start:Int()
		If m_socket <> Null
			If m_socket.Bind(m_port) = True
				m_socket.Listen(0)
				Return True
			End If
		End If
		Return False
	End Method

	Rem
		bbdoc: Close the Server and clear the Client list.
		returns: Nothing.
	End Rem
	Method Close()
		If m_socket <> Null
			m_socket.Close()
		End If
		For Local client:TClient = EachIn m_clients
			DisconnectClient(client)
		Next
	End Method

	Rem
		bbdoc: Check if the Server is open to Clients.
		returns: True if this Server is open to connections, or False if it is not.
	End Rem
	Method Connected:Int()
		Return m_socket.Connected()
	End Method

'#end region (Connection)

	Rem
		bbdoc: Add a Client to the Server.
		returns: True if the Client was added to the Server, or False if it was not.
	End Rem
	Method AddClient:Int(client:TClient)
		If client <> Null
			If client.Connected() = True
				client.m_link = m_clients.AddLast(client)
				OnClientAdd(client)
				Return True
			End If
		End If
		Return False
	End Method

	Rem
		bbdoc: Disconnect a Client from the Server.
		returns: Nothing.
	End Rem
	Method DisconnectClient(client:TClient, ondisconnect:Int = True)
		If ondisconnect = True
			OnClientDisconnect(client)
		End If

		client.Disconnect()
		client.m_link.Remove()
	End Method

	Rem
		bbdoc: Handle a new Client (if there are any attempting to connect).
		returns: Nothing.
	End Rem
	Method HandleNewClients:Int()
		Local acceptsocket:TSocket, client:TClient

		acceptsocket = m_socket.Accept(m_accept_timeout)
		If acceptsocket <> Null
			client = OnSocketAccept(acceptsocket)
			Return AddClient(client)
		End If
		Return False
	End Method

	Rem
		bbdoc: Check for and handle incoming data from the Clients.
		returns: Nothing.
		about: This calls HandleIncomingData if there is data to be read from a Client.
	End Rem
	Method CheckTransmissions()
		For Local client:TClient = EachIn m_clients
			If client.Eof() = False
				If client.ReadAvail() > 0
					HandleIncomingData(client)
				End If
			Else
				DisconnectClient(client)
			End If
		Next
	End Method

	Rem
		bbdoc: Handle incoming data from a Client.
		returns: Nothing.
	End Rem
	Method HandleIncomingData(client:TClient)
		Local msgid:Int, message:TNetMessage

		While client.ReadAvail() > 0
			msgid = Int(client.ReadByte())
			message = m_msgmap.GetMessageByID(msgid)
			client.HandleMessage(msgid, message)
		Wend
	End Method

	Rem
		bbdoc: Called when a Client is added to the Server (implement in extending types).
		returns: Nothing.
	End Rem
	Method OnClientAdd(client:TClient) Abstract

	Rem
		bbdoc: Called when a Client is disconnected from the Server (implement in extending types).
		returns: Nothing.
	End Rem
	Method OnClientDisconnect(client:TClient) Abstract

	Rem
		bbdoc: Called when a socket is accepted to the Server (implement in extending types).
		returns: A Client, or Null if the socket was further unaccepted.
	End Rem
	Method OnSocketAccept:TClient(socket:TSocket) Abstract

End Type

Rem
	Copyright (c) 2009 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.
	-----------------------------------------------------------------------------

	server.bmx (Contains: TServer, )

End Rem

Rem
	bbdoc: duct generic server type.
End Rem
Type TServer

	Field m_socket:TSocket
	Field m_port:Int, m_accept_timeout:Int

	Field m_msgmap:TNetMessageMap
	Field m_clients:TListEx

	Method New()
		m_clients = New TListEx
	End Method

	Method _init(msgmap:TNetMessageMap, socket:TSocket, port:Int, accept_timeout:Int)
		SetMessageMap(msgmap)
		SetSocket(socket)
		SetPort(port)
		SetAcceptTimeout(accept_timeout)
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the timeout when the Server attempts to accept a Client.
		returns: Nothing.
	End Rem
	Method SetAcceptTimeout(accept_timeout:Int)
		m_accept_timeout = accept_timeout
	End Method

	Rem
		bbdoc: Get the timout (ms) used when the Server attempts to accept a Client.
		returns: The accept timeout for this Server.
	End Rem
	Method GetAcceptTimeout:Int()
		Return m_accept_timeout
	End Method

	Rem
		bbdoc: Set the Server's port.
		returns: Nothing.
	End Rem
	Method SetPort(port:Int)
		m_port = port
	End Method

	Rem
		bbdoc: Get the Server's port.
		returns: The port for this Server.
	End Rem
	Method GetPort:Int()
		Return m_port
	End Method

	Rem
		bbdoc: Set the socket for the Server.
		returns: Nothing.
		about: This will close all existing client connections.
	End Rem
	Method SetSocket(socket:TSocket)
		Close()
		m_socket = socket
	End Method

	Rem
		bbdoc: Get the Socket for the Server.
		returns: The socket for this Server.
	End Rem
	Method GetSocket:TSocket()
		Return m_socket
	End Method

	Rem
		bbdoc: Set the MessageMap for the Server.
		returns: Nothing.
	End Rem
	Method SetMessageMap(msgmap:TNetMessageMap)
		m_msgmap = msgmap
	End Method

	Rem
		bbdoc: Get the MessageMap for the Server.
		returns: The MessageMap for this server.
	End Rem
	Method GetMessageMap:TNetMessageMap()
		Return m_msgmap
	End Method

'#end region (Field accessors)

'#region Connection

	Rem
		bbdoc: Start the server connection.
		returns: True if the socket was able to open on the Server's port, or False if it was unable.
	End Rem
	Method Start:Int()
		If m_socket <> Null
			If m_socket.Bind(m_port) = True
				m_socket.Listen(0)
				Return True
			End If
		End If
		Return False
	End Method

	Rem
		bbdoc: Close the Server and clear the Client list.
		returns: Nothing.
	End Rem
	Method Close()
		If m_socket <> Null
			m_socket.Close()
		End If
		For Local client:TClient = EachIn m_clients
			DisconnectClient(client)
		Next
	End Method

	Rem
		bbdoc: Check if the Server is open to Clients.
		returns: True if this Server is open to connections, or False if it is not.
	End Rem
	Method Connected:Int()
		Return m_socket.Connected()
	End Method

'#end region (Connection)

	Rem
		bbdoc: Add a Client to the Server.
		returns: True if the Client was added to the Server, or False if it was not.
	End Rem
	Method AddClient:Int(client:TClient)
		If client <> Null
			If client.Connected() = True
				client.m_link = m_clients.AddLast(client)
				OnClientAdd(client)
				Return True
			End If
		End If
		Return False
	End Method

	Rem
		bbdoc: Disconnect a Client from the Server.
		returns: Nothing.
	End Rem
	Method DisconnectClient(client:TClient, ondisconnect:Int = True)
		If ondisconnect = True
			OnClientDisconnect(client)
		End If

		client.Disconnect()
		client.m_link.Remove()
	End Method

	Rem
		bbdoc: Handle a new Client (if there are any attempting to connect).
		returns: Nothing.
	End Rem
	Method HandleNewClients:Int()
		Local acceptsocket:TSocket, client:TClient

		acceptsocket = m_socket.Accept(m_accept_timeout)
		If acceptsocket <> Null
			client = OnSocketAccept(acceptsocket)
			Return AddClient(client)
		End If
		Return False
	End Method

	Rem
		bbdoc: Check for and handle incoming data from the Clients.
		returns: Nothing.
		about: This calls HandleIncomingData if there is data to be read from a Client.
	End Rem
	Method CheckTransmissions()
		For Local client:TClient = EachIn m_clients
			If client.Eof() = False
				If client.ReadAvail() > 0
					HandleIncomingData(client)
				End If
			Else
				DisconnectClient(client)
			End If
		Next
	End Method

	Rem
		bbdoc: Handle incoming data from a Client.
		returns: Nothing.
	End Rem
	Method HandleIncomingData(client:TClient)
		Local msgid:Int, message:TNetMessage

		While client.ReadAvail() > 0
			msgid = Int(client.ReadByte())
			message = m_msgmap.GetMessageByID(msgid)
			client.HandleMessage(msgid, message)
		Wend
	End Method

	Rem
		bbdoc: Called when a Client is added to the Server (implement in extending types).
		returns: Nothing.
	End Rem
	Method OnClientAdd(client:TClient) Abstract

	Rem
		bbdoc: Called when a Client is disconnected from the Server (implement in extending types).
		returns: Nothing.
	End Rem
	Method OnClientDisconnect(client:TClient) Abstract

	Rem
		bbdoc: Called when a socket is accepted to the Server (implement in extending types).
		returns: A Client, or Null if the socket was further unaccepted.
	End Rem
	Method OnSocketAccept:TClient(m_socket:TSocket) Abstract

End Type


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

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

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


Whitespace

Whitespace

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.02"
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: 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


TSurface.HasAlpha now uses TBrush.HasAlpha

Strict

Import MaxB3D.Math
Import "brush.bmx"

Const SURFACE_POS = 1
Const SURFACE_NML = 2
Const SURFACE_CLR = 4
Const SURFACE_TEX = 8
Const SURFACE_TRI = 16
Const SURFACE_ALL = SURFACE_POS|SURFACE_NML|SURFACE_CLR|SURFACE_TEX|SURFACE_TRI

Type TSurface
	Field _brush:TBrush=New TBrush

	Field _vertexcnt,_trianglecnt
	Field _vertexpos#[],_vertexnml#[],_vertexclr#[]
	Field _vertextex#[][],_texcoordsize=-1

	Field _triangle[]

	Field _res:TSurfaceRes,_reset=-1

	Field _resetbounds=True,_minx#,_miny#,_minz#,_maxx#,_maxy#,_maxz#
	Field _boundsupdatedmsg:TMaxB3DMsg=New TMaxB3DMsg

	Method Copy:TSurface(data=SURFACE_ALL)
		Local surface:TSurface=New TSurface
		surface._brush.Load(_brush)
		surface._vertexcnt=_vertexcnt
		If data&SURFACE_POS surface._vertexpos=_vertexpos[..]
		If data&SURFACE_NML surface._vertexnml=_vertexnml[..]
		If data&SURFACE_CLR surface._vertexclr=_vertexclr[..]
		If data&SURFACE_TEX surface._vertextex=_vertextex[..];surface._texcoordsize=_texcoordsize
		If data&SURFACE_TRI surface._trianglecnt=_trianglecnt;surface._triangle=_triangle[..]
		Return surface
	End Method

	Method Resize(vertexcount,trianglecount)
		If vertexcount>-1
			_vertexpos=_vertexpos[..vertexcount*3]
			_vertexnml=_vertexnml[..vertexcount*3]
			_vertexclr=_vertexclr[..vertexcount*4]
			For Local i=_vertexcnt*4 To vertexcount*4-1
				_vertexclr[i]=1.0
			Next
			For Local i=0 To _texcoordsize-1
				_vertextex[i]=_vertextex[i][..vertexcount*2]
			Next
			_vertexcnt=vertexcount
		EndIf

		If trianglecount>-1
			_triangle=_triangle[..trianglecount*3]
			_trianglecnt=trianglecount
		EndIf
	End Method

	Method GetSize(vertices Var,triangles Var)
		vertices=_vertexcnt
		triangles=_trianglecnt
	End Method

	Method AddVertex(x#,y#,z#,u#=0.0,v#=0.0)
		Resize(_vertexcnt+1,-1)
		SetCoords(_vertexcnt-1,x,y,z)
		SetTexCoords(_vertexcnt-1,u,v)
		Return _vertexcnt-1
	End Method

	Method GetCoords(index,x# Var,y# Var,z# Var)
		x=_vertexpos[index*3+0]
		y=_vertexpos[index*3+1]
		z=_vertexpos[index*3+2]
	End Method
	Method SetCoords(index,x#,y#,z#)
		_vertexpos[index*3+0]=x
		_vertexpos[index*3+1]=y
		_vertexpos[index*3+2]=z
		_reset:|1;_resetbounds=True
	End Method

	Method GetNormal(index,nx# Var,ny# Var,nz# Var)
		nx=_vertexnml[index*3+0]*-1
		ny=_vertexnml[index*3+1]*-1
		nz=_vertexnml[index*3+2]*-1
	End Method
	Method SetNormal(index,nx#,ny#,nz#)
		_vertexnml[index*3+0]=nx*-1
		_vertexnml[index*3+1]=ny*-1
		_vertexnml[index*3+2]=nz*-1
		_reset:|2
	End Method

	Method GetColor(index,red Var,green Var,blue Var,alpha# Var)
		red=_vertexclr[index*4+0]*255.0
		green=_vertexclr[index*4+1]*255.0
		blue=_vertexclr[index*4+2]*255.0
		alpha=_vertexclr[index*4+3]
	End Method
	Method SetColor(index,red,green,blue,alpha#)
		_vertexclr[index*4+0]=red/255.0
		_vertexclr[index*4+1]=green/255.0
		_vertexclr[index*4+2]=blue/255.0
		_vertexclr[index*4+3]=alpha
		_reset:|4
	End Method

	Method GetTexCoords(index,u# Var,v# Var,set=0)
		ResizeTexSets set
		u=_vertextex[set][index*2+0]
		v=_vertextex[set][index*2+1]
	End Method
	Method SetTexCoords(index,u#,v#,set=0)
		ResizeTexSets set
		_vertextex[set][index*2+0]=u
		_vertextex[set][index*2+1]=v
		_reset:|Int(2^(4+set))
	End Method

	Method ResizeTexSets(set)
		If set+1>_texcoordsize
			Local size=_vertextex.length
			_vertextex=_vertextex[..set+1]
			For Local i=size To set
				_vertextex[i]=New Float[_vertexcnt*2]
			Next
			_texcoordsize=set+1
		EndIf
	End Method

	Method AddTriangle(v0,v1,v2)
		Resize(-1,_trianglecnt+1)
		SetTriangle _trianglecnt-1,v0,v1,v2
		Return _trianglecnt-1
	End Method

	Method GetTriangle(index,v0 Var,v1 Var,v2 Var)
		v0=_triangle[index*3+0]
		v1=_triangle[index*3+1]
		v2=_triangle[index*3+2]
	End Method
	Method SetTriangle(index,v0,v1,v2)
		_triangle[index*3+0]=v0
		_triangle[index*3+1]=v1
		_triangle[index*3+2]=v2
		_reset:|8
	End Method

	Method Flip()
		For Local t=0 To _trianglecnt-1
			Local v2=_triangle[t*3+2]
			_triangle[t*3+2]=_triangle[t*3+0]
			_triangle[t*3+0]=v2
		Next
		For Local v=0 To _vertexcnt-1
			_vertexnml[(v*3)+0]:*-1
			_vertexnml[(v*3)+1]:*-1
			_vertexnml[(v*3)+2]:*-1
		Next
		_reset:|2|8
	End Method

	Method Transform(matrix:TMatrix)
		For Local i=0 To _vertexcnt-1
			matrix.TransformVec3 _vertexpos[i*3+0],_vertexpos[i*3+1],_vertexpos[i*3+2]
			'matrix.TransformVector _vertexnml[i+0],_vertexnml[i+1],_vertexnml[i+2],w
		Next
	End Method

	Method UpdateBounds(force=False)
		If Not _resetbounds And force=false Return
		_minx=999999999;_miny=999999999;_minz=999999999
		_maxx=-999999999;_maxy=-999999999;_maxz=-999999999
		For Local v=0 To _vertexcnt-1
			Local x#,y#,z#
			GetCoords v,x,y,z
			_minx=Min(x,_minx);_maxx=Max(x,_maxx)
			_miny=Min(y,_miny);_maxy=Max(y,_maxy)
			_minz=Min(z,_minz);_maxz=Max(z,_maxz)
		Next
		_boundsupdatedmsg.Run
		_resetbounds=False
	End Method

	Method UpdateNormals(smoothing=True)
		If smoothing
			C_UpdateNormals(_trianglecnt,_vertexcnt,_triangle,_vertexpos,_vertexnml)
		Else
			Local face_normal:TVector[_trianglecnt],vertex_triangles[][_vertexcnt]

			For Local i=0 To _trianglecnt-1
				Local v0,v1,v2
				GetTriangle i,v0,v1,v2

				vertex_triangles[v0]:+[i]
				vertex_triangles[v1]:+[i]
				vertex_triangles[v2]:+[i]

				Local a:TVector=New TVector,b:TVector=New TVector,c:TVector=New TVector
				GetCoords v0,a.x,a.y,a.z
				GetCoords v1,b.x,b.y,b.z
				GetCoords v2,c.x,c.y,c.z

				face_normal[i]=New TVector.FromTriangle(a,b,c)
			Next

			Local normal:TVector=New TVector
			For Local i=0 To _vertexcnt-1
				normal.Create3(0,0,0)
				For Local t=0 To vertex_triangles[i].length-1
					normal=normal.Add(face_normal[vertex_triangles[i][t]])
				Next
				normal.Normalize()
				SetNormal i,-normal.x,-normal.y,-normal.z
			Next
		EndIf
		_reset:|2
	End Method

	Method SetTriangleNormal(index,nx#,ny#,nz#)
		Local v0,v1,v2
		GetTriangle index,v0,v1,v2
		SetNormal v0,nx,ny,nz
		SetNormal v1,nx,ny,nz
		SetNormal v2,nx,ny,nz
	End Method

	Method GetBrush:TBrush()
		Return _brush.Copy()
	End Method
	Method SetBrush(brush:TBrush)
		_brush.Load(brush)
	End Method

	Method CountVertices()
		Return _vertexcnt
	End Method
	Method CountTriangles()
		Return _trianglecnt
	End Method

	Method IsEmpty()
		Return _vertexcnt=0 And _trianglecnt=0
	End Method

	Method HasAlpha()
		Return _brush.HasAlpha()
	End Method
End Type

Type TSurfaceRes Extends TDriverResource
	Field _vertexcnt
	Field _trianglecnt

	Method Copy:TSurfaceRes() Abstract
End Type

Type TMaxB3DMsg
	Field _succ:TMaxB3DMsg
	Field _context:Object
	Field _func(context:Object)

	Method Add(func(context:Object),context:Object)
		Local msg:TMaxB3DMsg=Self
		While msg._succ
			msg=msg._succ
		Wend
		msg._succ=New TMaxB3DMsg
		msg._context=context
		msg._func=func
	End Method

	Method Run()
		Local msg:TMaxB3DMsg=Self
		While msg
			If msg._func msg._func(msg._context)
			msg=msg._succ
		Wend
	End Method
End Type

Strict

Import MaxB3D.Math
Import "brush.bmx"

Const SURFACE_POS = 1
Const SURFACE_NML = 2
Const SURFACE_CLR = 4
Const SURFACE_TEX = 8
Const SURFACE_TRI = 16
Const SURFACE_ALL = SURFACE_POS|SURFACE_NML|SURFACE_CLR|SURFACE_TEX|SURFACE_TRI

Type TSurface
	Field _brush:TBrush=New TBrush

	Field _vertexcnt,_trianglecnt
	Field _vertexpos#[],_vertexnml#[],_vertexclr#[]
	Field _vertextex#[][],_texcoordsize=-1

	Field _triangle[]

	Field _res:TSurfaceRes,_reset=-1

	Field _resetbounds=True,_minx#,_miny#,_minz#,_maxx#,_maxy#,_maxz#
	Field _boundsupdatedmsg:TMaxB3DMsg=New TMaxB3DMsg

	Method Copy:TSurface(data=SURFACE_ALL)
		Local surface:TSurface=New TSurface
		surface._brush.Load(_brush)
		surface._vertexcnt=_vertexcnt
		If data&SURFACE_POS surface._vertexpos=_vertexpos[..]
		If data&SURFACE_NML surface._vertexnml=_vertexnml[..]
		If data&SURFACE_CLR surface._vertexclr=_vertexclr[..]
		If data&SURFACE_TEX surface._vertextex=_vertextex[..];surface._texcoordsize=_texcoordsize
		If data&SURFACE_TRI surface._trianglecnt=_trianglecnt;surface._triangle=_triangle[..]
		Return surface
	End Method

	Method Resize(vertexcount,trianglecount)
		If vertexcount>-1
			_vertexpos=_vertexpos[..vertexcount*3]
			_vertexnml=_vertexnml[..vertexcount*3]
			_vertexclr=_vertexclr[..vertexcount*4]
			For Local i=_vertexcnt*4 To vertexcount*4-1
				_vertexclr[i]=1.0
			Next
			For Local i=0 To _texcoordsize-1
				_vertextex[i]=_vertextex[i][..vertexcount*2]
			Next
			_vertexcnt=vertexcount
		EndIf

		If trianglecount>-1
			_triangle=_triangle[..trianglecount*3]
			_trianglecnt=trianglecount
		EndIf
	End Method

	Method GetSize(vertices Var,triangles Var)
		vertices=_vertexcnt
		triangles=_trianglecnt
	End Method

	Method AddVertex(x#,y#,z#,u#=0.0,v#=0.0)
		Resize(_vertexcnt+1,-1)
		SetCoords(_vertexcnt-1,x,y,z)
		SetTexCoords(_vertexcnt-1,u,v)
		Return _vertexcnt-1
	End Method

	Method GetCoords(index,x# Var,y# Var,z# Var)
		x=_vertexpos[index*3+0]
		y=_vertexpos[index*3+1]
		z=_vertexpos[index*3+2]
	End Method
	Method SetCoords(index,x#,y#,z#)
		_vertexpos[index*3+0]=x
		_vertexpos[index*3+1]=y
		_vertexpos[index*3+2]=z
		_reset:|1;_resetbounds=True
	End Method

	Method GetNormal(index,nx# Var,ny# Var,nz# Var)
		nx=_vertexnml[index*3+0]*-1
		ny=_vertexnml[index*3+1]*-1
		nz=_vertexnml[index*3+2]*-1
	End Method
	Method SetNormal(index,nx#,ny#,nz#)
		_vertexnml[index*3+0]=nx*-1
		_vertexnml[index*3+1]=ny*-1
		_vertexnml[index*3+2]=nz*-1
		_reset:|2
	End Method

	Method GetColor(index,red Var,green Var,blue Var,alpha# Var)
		red=_vertexclr[index*4+0]*255.0
		green=_vertexclr[index*4+1]*255.0
		blue=_vertexclr[index*4+2]*255.0
		alpha=_vertexclr[index*4+3]
	End Method
	Method SetColor(index,red,green,blue,alpha#)
		_vertexclr[index*4+0]=red/255.0
		_vertexclr[index*4+1]=green/255.0
		_vertexclr[index*4+2]=blue/255.0
		_vertexclr[index*4+3]=alpha
		_reset:|4
	End Method

	Method GetTexCoords(index,u# Var,v# Var,set=0)
		ResizeTexSets set
		u=_vertextex[set][index*2+0]
		v=_vertextex[set][index*2+1]
	End Method
	Method SetTexCoords(index,u#,v#,set=0)
		ResizeTexSets set
		_vertextex[set][index*2+0]=u
		_vertextex[set][index*2+1]=v
		_reset:|Int(2^(4+set))
	End Method

	Method ResizeTexSets(set)
		If set+1>_texcoordsize
			Local size=_vertextex.length
			_vertextex=_vertextex[..set+1]
			For Local i=size To set
				_vertextex[i]=New Float[_vertexcnt*2]
			Next
			_texcoordsize=set+1
		EndIf
	End Method

	Method AddTriangle(v0,v1,v2)
		Resize(-1,_trianglecnt+1)
		SetTriangle _trianglecnt-1,v0,v1,v2
		Return _trianglecnt-1
	End Method

	Method GetTriangle(index,v0 Var,v1 Var,v2 Var)
		v0=_triangle[index*3+0]
		v1=_triangle[index*3+1]
		v2=_triangle[index*3+2]
	End Method
	Method SetTriangle(index,v0,v1,v2)
		_triangle[index*3+0]=v0
		_triangle[index*3+1]=v1
		_triangle[index*3+2]=v2
		_reset:|8
	End Method

	Method Flip()
		For Local t=0 To _trianglecnt-1
			Local v2=_triangle[t*3+2]
			_triangle[t*3+2]=_triangle[t*3+0]
			_triangle[t*3+0]=v2
		Next
		For Local v=0 To _vertexcnt-1
			_vertexnml[(v*3)+0]:*-1
			_vertexnml[(v*3)+1]:*-1
			_vertexnml[(v*3)+2]:*-1
		Next
		_reset:|2|8
	End Method

	Method Transform(matrix:TMatrix)
		For Local i=0 To _vertexcnt-1
			matrix.TransformVec3 _vertexpos[i*3+0],_vertexpos[i*3+1],_vertexpos[i*3+2]
			'matrix.TransformVector _vertexnml[i+0],_vertexnml[i+1],_vertexnml[i+2],w
		Next
	End Method

	Method UpdateBounds(force=False)
		If Not _resetbounds And force=false Return
		_minx=999999999;_miny=999999999;_minz=999999999
		_maxx=-999999999;_maxy=-999999999;_maxz=-999999999
		For Local v=0 To _vertexcnt-1
			Local x#,y#,z#
			GetCoords v,x,y,z
			_minx=Min(x,_minx);_maxx=Max(x,_maxx)
			_miny=Min(y,_miny);_maxy=Max(y,_maxy)
			_minz=Min(z,_minz);_maxz=Max(z,_maxz)
		Next
		_boundsupdatedmsg.Run
		_resetbounds=False
	End Method

	Method UpdateNormals(smoothing=True)
		If smoothing
			C_UpdateNormals(_trianglecnt,_vertexcnt,_triangle,_vertexpos,_vertexnml)
		Else
			Local face_normal:TVector[_trianglecnt],vertex_triangles[][_vertexcnt]

			For Local i=0 To _trianglecnt-1
				Local v0,v1,v2
				GetTriangle i,v0,v1,v2

				vertex_triangles[v0]:+[i]
				vertex_triangles[v1]:+[i]
				vertex_triangles[v2]:+[i]

				Local a:TVector=New TVector,b:TVector=New TVector,c:TVector=New TVector
				GetCoords v0,a.x,a.y,a.z
				GetCoords v1,b.x,b.y,b.z
				GetCoords v2,c.x,c.y,c.z

				face_normal[i]=New TVector.FromTriangle(a,b,c)
			Next

			Local normal:TVector=New TVector
			For Local i=0 To _vertexcnt-1
				normal.Create3(0,0,0)
				For Local t=0 To vertex_triangles[i].length-1
					normal=normal.Add(face_normal[vertex_triangles[i][t]])
				Next
				normal.Normalize()
				SetNormal i,-normal.x,-normal.y,-normal.z
			Next
		EndIf
		_reset:|2
	End Method

	Method SetTriangleNormal(index,nx#,ny#,nz#)
		Local v0,v1,v2
		GetTriangle index,v0,v1,v2
		SetNormal v0,nx,ny,nz
		SetNormal v1,nx,ny,nz
		SetNormal v2,nx,ny,nz
	End Method

	Method GetBrush:TBrush()
		Return _brush.Copy()
	End Method
	Method SetBrush(brush:TBrush)
		_brush.Load(brush)
	End Method

	Method CountVertices()
		Return _vertexcnt
	End Method
	Method CountTriangles()
		Return _trianglecnt
	End Method

	Method IsEmpty()
		Return _vertexcnt=0 And _trianglecnt=0
	End Method

	Method HasAlpha()
		Return _brush._a<>1 Or _brush._fx&FX_FORCEALPHA
	End Method
End Type

Type TSurfaceRes Extends TDriverResource
	Field _vertexcnt
	Field _trianglecnt

	Method Copy:TSurfaceRes() Abstract
End Type

Type TMaxB3DMsg
	Field _succ:TMaxB3DMsg
	Field _context:Object
	Field _func(context:Object)

	Method Add(func(context:Object),context:Object)
		Local msg:TMaxB3DMsg=Self
		While msg._succ
			msg=msg._succ
		Wend
		msg._succ=New TMaxB3DMsg
		msg._context=context
		msg._func=func
	End Method

	Method Run()
		Local msg:TMaxB3DMsg=Self
		While msg
			If msg._func msg._func(msg._context)
			msg=msg._succ
		Wend
	End Method
End Type


Using IsSuccess to see if http request succeeded

Rem
	bbdoc: Maximus 'update' argument implementation.
End Rem
Type mxUpdateImpl Extends dArgumentImplementation

	Field m_sourcesurl:String

	Method New()
		init(["update"])
	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()
		CheckOptions()
	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.update.usage")
	End Method

	Rem
		bbdoc: Execute the implementation's operation.
		returns: Nothing.
	End Rem
	Method Execute()
		If mainapp.m_sourcesupdated Then Return
		If m_sourcesurl Then m_sourcesurl = mainapp.m_sourcesurl
		If m_sourcesurl
			ThrowError(_s("error.update.nourl"))
		End If
		mainapp.m_sourcesupdated = True
		Local file:String = mainapp.m_sourcesfile + ".tmp"
		logger.LogMessage("fetching: " + m_sourcesurl + " -> " + file + "~t", False)
		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(m_sourcesurl, ["User-Agent: " + mainapp.m_useragent], "GET")
			Catch e:Object
				stream.Close()
				DeleteFile(file)
				logger.LogMessage("")
				ThrowError(_s("error.fetch.sources", [e.ToString()]))
			End Try
			stream.Close()
			If response.IsSuccess()
				logger.LogMessage(_s("message.fetch.done", [String(response.responseCode)]))
				CopyFile(file, mainapp.m_sourcesfile)
				DeleteFile(file)
			Else
				DeleteFile(file)
				logger.LogMessage("")
				ThrowError(_s("error.fetch.sources", ["Bad response code: " + String(response.responseCode)]))
			End If
		Else
			ThrowError(_s("error.writeperms", [file]))
		End If
	End Method

	Rem
		bbdoc: Check the options given to the command.
		returns: Nothing.
	End Rem
	Method CheckOptions()
		m_sourcesurl = Null
		For Local opt:dIdentifier = EachIn m_args
			Select opt.GetName().ToLower()
				Case "--url"
					If opt.GetChildCount() = 1
						m_sourcesurl = dStringVariable(opt.GetValueAtIndex(0)).Get()
					Else
						ThrowCommonError(mxOptErrors.REQUIRESPARAMS, opt.GetName())
					End If
				Default ThrowCommonError(mxOptErrors.UNKNOWN, opt.GetName())
			End Select
		Next
	End Method

	Rem
		bbdoc: Progress callback for sources 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 'update' argument implementation.
End Rem
Type mxUpdateImpl Extends dArgumentImplementation

	Field m_sourcesurl:String

	Method New()
		init(["update"])
	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()
		CheckOptions()
	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.update.usage")
	End Method

	Rem
		bbdoc: Execute the implementation's operation.
		returns: Nothing.
	End Rem
	Method Execute()
		If mainapp.m_sourcesupdated Then Return
		If m_sourcesurl Then m_sourcesurl = mainapp.m_sourcesurl
		If m_sourcesurl
			ThrowError(_s("error.update.nourl"))
		End If
		mainapp.m_sourcesupdated = True
		Local file:String = mainapp.m_sourcesfile + ".tmp"
		logger.LogMessage("fetching: " + m_sourcesurl + " -> " + file + "~t", False)
		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(m_sourcesurl, ["User-Agent: " + mainapp.m_useragent], "GET")
			Catch e:Object
				stream.Close()
				DeleteFile(file)
				logger.LogMessage("")
				ThrowError(_s("error.fetch.sources", [e.ToString()]))
			End Try
			stream.Close()
			If response.responseCode = 200
				logger.LogMessage(_s("message.fetch.done", [String(response.responseCode)]))
				CopyFile(file, mainapp.m_sourcesfile)
				DeleteFile(file)
			Else
				DeleteFile(file)
				logger.LogMessage("")
				ThrowError(_s("error.fetch.sources", ["Bad response code: " + String(response.responseCode)]))
			End If
		Else
			ThrowError(_s("error.writeperms", [file]))
		End If
	End Method

	Rem
		bbdoc: Check the options given to the command.
		returns: Nothing.
	End Rem
	Method CheckOptions()
		m_sourcesurl = Null
		For Local opt:dIdentifier = EachIn m_args
			Select opt.GetName().ToLower()
				Case "--url"
					If opt.GetChildCount() = 1
						m_sourcesurl = dStringVariable(opt.GetValueAtIndex(0)).Get()
					Else
						ThrowCommonError(mxOptErrors.REQUIRESPARAMS, opt.GetName())
					End If
				Default ThrowCommonError(mxOptErrors.UNKNOWN, opt.GetName())
			End Select
		Next
	End Method

	Rem
		bbdoc: Progress callback for sources 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


Now using meta.maximus file when calling GetInstalledVersionFromVerID

refs gh-1

Rem
	bbdoc: Maximus module utilities.
End Rem
Type mxModUtils

	Global g_modules:dObjectMap

	Rem
		bbdoc: Get an object map of the current modules.
		returns: An object map containing the current modules.
		about: @force can be used to force re-enumeration of the modules.
	End Rem
	Function GetModules:dObjectMap(force:Int = False)
		If Not g_modules Or force
			g_modules = EnumModules(Null, Null)
		End If
		Return g_modules
	End Function

	Rem
		bbdoc: Check if the user currently has the given module.
		returns: True if the given module was found.
	End Rem
	Function HasModule:Int(modul:String)
		Return GetModules()._Contains(modul)
	End Function

	Rem
		bbdoc: Get the path to the given module or modscope.
		returns: The path to the given module or modscope, or the modules path if the given modid is Null.
	End Rem
	Function ModulePath:String(modid:String)
		Local p:String = mainapp.m_modpath
		If modid Then p:+ "/" + modid.Replace(".", ".mod/") + ".mod"
		Return p
	End Function

	Rem
		bbdoc: Get the path to the main source file for the given module id .
		returns: The path to the given module's main source file, or Null if the given module id is Null.
	End Rem
	Function SourceFilePath:String(modid:String)
		If modid
			Return ModulePath(modid) + "/" + GetNameFromID(modid) + ".bmx"
		End If
		Return Null
	End Function

	Rem
		bbdoc: Get the module scope from the given versioned-module id (for modules with forced versions (or normal ids), e.g. mxModUtils.GetScopeFromID("foo.bar/dev")  would return "foo").
		returns: The scope of the given versioned-module id, or Null if the given versioned-module id was incorrect.
	End Rem
	Function GetScopeFromID:String(verid:String)
		verid = GetIDFromVerID(verid)
		Local i:Int = verid.Find(".")
		If i > -1 Then Return verid[..i]
		Return Null
	End Function

	Rem
		bbdoc: Get the module name from the given versioned-module id (for modules with forced versions (or normal ids), e.g. mxModUtils.GetNameFromID("foo.bar/dev")  would return "bar").
		returns: The name of the given versioned-module id, or Null if the given versioned-module is was incorrect.
	End Rem
	Function GetNameFromID:String(verid:String)
		verid = GetIDFromVerID(verid)
		Local i:Int = verid.Find(".")
		If i > -1 Then Return verid[i + 1..]
		Return Null
	End Function

	Rem
		bbdoc: Get the actual module id from the given versioned-module id (for modules with forced versions (or normal ids), e.g. mxModUtils.GetIDFromVerID("foo.bar/dev")  would return "foo.bar").
		returns: The id of the given versioned-module id.
	End Rem
	Function GetIDFromVerID:String(verid:String)
		Local i:Int = verid.Find("/")
		If i > -1 Then verid = verid[..i]
		Return verid
	End Function

	Rem
		bbdoc: Get the version from the given versioned-module id (for modules with forced versions, e.g. mxModUtils.GetVersionFromVerID("foo.bar/dev) would return "dev").
		returns: The version of the given versioned-module id (which will be Null if the given value is not versioned).
	End Rem
	Function GetVersionFromVerID:String(verid:String)
		Local i:Int = verid.Find("/")
		If i > -1 Then Return verid[i + 1..]
		Return Null
	End Function

	Rem
		bbdoc: Get the version of the module from the given versioned-module id, if it is installed.
		returns: The module's version (which may be "dev" if Subversion or git is found controlling the module's directory), or Null if the module was not found.
	End Rem
	Function GetInstalledVersionFromVerID:String(verid:String)
		verid = GetIDFromVerID(verid)
		'DebugLog("(mxModUtils.GetInstalledVersionFromVerID) modid: " + verid)
		Local ver:String
		Local path:String = mxModUtils.ModulePath(verid) + "/meta.maximus"
		If FileType(Path) = FILETYPE_FILE
			Local metafile:mxMetaFile = New mxMetaFile.Create(path)
			metafile.Load()
			ver = metafile.m_version
		End If
		Return ver
	End Function

	Rem
		bbdoc: Enumerate all of the current modules.
		returns: An object map containing the current modules.
	End Rem
	Function EnumModules:dObjectMap(modid:String = Null, mods:dObjectMap = Null)
		If Not mods Then mods = New dObjectMap
		Local dir:String = ModulePath(modid)
		Local files:String[] = LoadDir(dir)
		For Local file:String = EachIn files
			Local path:String = dir + "/" + file
			If file[file.length - 4..] <> ".mod" Or FileType(path) <> FILETYPE_DIR Then Continue
			Local t:String = file[..file.length - 4]
			If modid Then t = modid + "." + t
			Local i:Int = t.Find(".")
			If i > -1 And t.Find(".", i + 1) = -1 Then mods._Insert(t, path)
			EnumModules(t, mods)
		Next
		Return mods
	End Function

	Rem
		bbdoc: Document all modules, or the modules given (doesn't work yet - all documentation will be built).
		returns: The exit code from makedocs.
	End Rem
	Function DocMods:Int(args:String = Null)
		args = "docmods " + args.Trim()
		logger.LogMessage("run: " + args)
		Return system_(mainapp.m_binpath + "/" + args)
	End Function

End Type

Rem
	bbdoc: Maximus BMK utilities.
End Rem
Type mxBMKUtils

	Rem
		bbdoc: Run BMK with the given arguments.
		returns: The exit code from bmk (non-zero when an error occured).
	End Rem
	Function RunBMK:Int(args:String)
		args = "bmk " + args.Trim()
		logger.LogMessage("run: " + args)
		Return system_(mainapp.m_binpath + "/" + args)
	End Function

	Rem
		bbdoc: Make modules with the given arguments.
		returns: The exit code from bmk (non-zero when an error occured).
	End Rem
	Function MakeMods:Int(args:String, threaded:Int)
		Local opts:String
		If threaded Then opts:+ " -h"
		Return RunBMK("makemods" + opts + " " + args)
	End Function

End Type

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

	Field m_progress:Int = 0

End Type

Rem
	bbdoc: Maximus module utilities.
End Rem
Type mxModUtils

	Global g_modules:dObjectMap

	Rem
		bbdoc: Get an object map of the current modules.
		returns: An object map containing the current modules.
		about: @force can be used to force re-enumeration of the modules.
	End Rem
	Function GetModules:dObjectMap(force:Int = False)
		If Not g_modules Or force
			g_modules = EnumModules(Null, Null)
		End If
		Return g_modules
	End Function

	Rem
		bbdoc: Check if the user currently has the given module.
		returns: True if the given module was found.
	End Rem
	Function HasModule:Int(modul:String)
		Return GetModules()._Contains(modul)
	End Function

	Rem
		bbdoc: Get the path to the given module or modscope.
		returns: The path to the given module or modscope, or the modules path if the given modid is Null.
	End Rem
	Function ModulePath:String(modid:String)
		Local p:String = mainapp.m_modpath
		If modid Then p:+ "/" + modid.Replace(".", ".mod/") + ".mod"
		Return p
	End Function

	Rem
		bbdoc: Get the path to the main source file for the given module id .
		returns: The path to the given module's main source file, or Null if the given module id is Null.
	End Rem
	Function SourceFilePath:String(modid:String)
		If modid
			Return ModulePath(modid) + "/" + GetNameFromID(modid) + ".bmx"
		End If
		Return Null
	End Function

	Rem
		bbdoc: Get the module scope from the given versioned-module id (for modules with forced versions (or normal ids), e.g. mxModUtils.GetScopeFromID("foo.bar/dev")  would return "foo").
		returns: The scope of the given versioned-module id, or Null if the given versioned-module id was incorrect.
	End Rem
	Function GetScopeFromID:String(verid:String)
		verid = GetIDFromVerID(verid)
		Local i:Int = verid.Find(".")
		If i > -1 Then Return verid[..i]
		Return Null
	End Function

	Rem
		bbdoc: Get the module name from the given versioned-module id (for modules with forced versions (or normal ids), e.g. mxModUtils.GetNameFromID("foo.bar/dev")  would return "bar").
		returns: The name of the given versioned-module id, or Null if the given versioned-module is was incorrect.
	End Rem
	Function GetNameFromID:String(verid:String)
		verid = GetIDFromVerID(verid)
		Local i:Int = verid.Find(".")
		If i > -1 Then Return verid[i + 1..]
		Return Null
	End Function

	Rem
		bbdoc: Get the actual module id from the given versioned-module id (for modules with forced versions (or normal ids), e.g. mxModUtils.GetIDFromVerID("foo.bar/dev")  would return "foo.bar").
		returns: The id of the given versioned-module id.
	End Rem
	Function GetIDFromVerID:String(verid:String)
		Local i:Int = verid.Find("/")
		If i > -1 Then verid = verid[..i]
		Return verid
	End Function

	Rem
		bbdoc: Get the version from the given versioned-module id (for modules with forced versions, e.g. mxModUtils.GetVersionFromVerID("foo.bar/dev) would return "dev").
		returns: The version of the given versioned-module id (which will be Null if the given value is not versioned).
	End Rem
	Function GetVersionFromVerID:String(verid:String)
		Local i:Int = verid.Find("/")
		If i > -1 Then Return verid[i + 1..]
		Return Null
	End Function

	Rem
		bbdoc: Get the version of the module from the given versioned-module id, if it is installed.
		returns: The module's version (which may be "dev" if Subversion or git is found controlling the module's directory), or Null if the module was not found.
	End Rem
	Function GetInstalledVersionFromVerID:String(verid:String)
		verid = GetIDFromVerID(verid)
		'DebugLog("(mxModUtils.GetInstalledVersionFromVerID) modid: " + verid)
		Local ver:String
		Local path:String = mxModUtils.ModulePath(verid) + "/"
		If FileType(path) = FILETYPE_DIR
			If FileType(path + ".git") = FILETYPE_DIR Or FileType(path + ".svn") = FILETYPE_DIR
				ver = "dev"
			Else If FileType(path + GetNameFromID(verid) + ".bmx") = FILETYPE_FILE
				Local stream:TStream = ReadStream(path + GetNameFromID(verid) + ".bmx")
				If stream
					Local line:String
					While Not stream.Eof()
						line = stream.ReadLine().Trim().ToLower()
						If line.Contains("moduleinfo") And line.Contains("version")
							line = line.Replace(" ", "").Replace("~q", "")
							'Local vp:Int = line.Find("moduleinfoversion:")
							If line.StartsWith("moduleinfoversion:") ' vp > -1 And (vp + 18 <= line.Length)
								ver = line[18..]
								Exit
							End If
						End If
					End While
					stream.Close()
				End If
			End If
		End If
		Return ver
	End Function

	Rem
		bbdoc: Enumerate all of the current modules.
		returns: An object map containing the current modules.
	End Rem
	Function EnumModules:dObjectMap(modid:String = Null, mods:dObjectMap = Null)
		If Not mods Then mods = New dObjectMap
		Local dir:String = ModulePath(modid)
		Local files:String[] = LoadDir(dir)
		For Local file:String = EachIn files
			Local path:String = dir + "/" + file
			If file[file.length - 4..] <> ".mod" Or FileType(path) <> FILETYPE_DIR Then Continue
			Local t:String = file[..file.length - 4]
			If modid Then t = modid + "." + t
			Local i:Int = t.Find(".")
			If i > -1 And t.Find(".", i + 1) = -1 Then mods._Insert(t, path)
			EnumModules(t, mods)
		Next
		Return mods
	End Function

	Rem
		bbdoc: Document all modules, or the modules given (doesn't work yet - all documentation will be built).
		returns: The exit code from makedocs.
	End Rem
	Function DocMods:Int(args:String = Null)
		args = "docmods " + args.Trim()
		logger.LogMessage("run: " + args)
		Return system_(mainapp.m_binpath + "/" + args)
	End Function

End Type

Rem
	bbdoc: Maximus BMK utilities.
End Rem
Type mxBMKUtils

	Rem
		bbdoc: Run BMK with the given arguments.
		returns: The exit code from bmk (non-zero when an error occured).
	End Rem
	Function RunBMK:Int(args:String)
		args = "bmk " + args.Trim()
		logger.LogMessage("run: " + args)
		Return system_(mainapp.m_binpath + "/" + args)
	End Function

	Rem
		bbdoc: Make modules with the given arguments.
		returns: The exit code from bmk (non-zero when an error occured).
	End Rem
	Function MakeMods:Int(args:String, threaded:Int)
		Local opts:String
		If threaded Then opts:+ " -h"
		Return RunBMK("makemods" + opts + " " + args)
	End Function

End Type

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

	Field m_progress:Int = 0

End Type


Renamed FX_FORCEALPHA to FX_ALPHABLEND

Renamed FX_FORCEALPHA to FX_ALPHABLEND

Strict

Import Prime.Max2DEx
Import "texture.bmx"

Const FX_NONE        = 0
Const FX_FULLBRIGHT  = 1
Const FX_VERTEXCOLOR = 2
Const FX_FLATSHADED  = 4
Const FX_NOFOG	     = 8
Const FX_NOCULLING   = 16
Const FX_ALPHABLEND  = 32
Const FX_WIREFRAME   = 64

Type TBrush
	Field _name$
	Field _r#=1.0,_g#=1.0,_b#=1.0,_a#=1.0
	Field _shine#
	Field _texture:TTexture[8],_textureframe[8]
	Field _blend,_fx
	Field _shader:TShader

	Method Copy:TBrush()
		Local newbrush:TBrush=New TBrush
		newbrush.Load Self
		Return newbrush
	End Method

	Method Merge:TBrush(master:TBrush)
		Local red#,green#,blue#,alpha#,shine#,blend,fx,shader:TShader
		red=master._r;green=master._g;blue=master._b;alpha=master._a
		blend=master._blend;fx=master._fx;shader=master._shader

		red:*_r;green:*_g;blue:*_b;alpha:*_a
		Local shine2#=_shine
		If shine=0.0 Then shine=shine2
		If shine<>0.0 And shine2<>0.0 Then shine:*shine2
		If blend=0 Then blend=_blend
		fx:|_fx
		If shader=Null shader=_shader

		Local newbrush:TBrush=New TBrush
		newbrush.SetColor red*255,green*255,blue*255
		newbrush.SetAlpha alpha
		newbrush.SetShine shine
		newbrush.SetBlend blend
		newbrush.SetFX fx
		newbrush.SetShader shader

		For Local i=0 To 7
			newbrush.SetTexture _texture[i],i,_textureframe[i]
			If master._texture[i] newbrush.SetTexture master._texture[i],i,master._textureframe[i]
		Next

		Return newbrush
	End Method

	Method Load(brush:TBrush)
		If brush=Null brush=New TBrush
		_r=brush._r;_g=brush._g;_b=brush._b;_a=brush._a
		_shine=brush._shine
		For Local i=0 To 7
			_texture[i]=brush._texture[i]
			_textureframe[i]=brush._textureframe[i]
		Next
		_blend=brush._blend;_fx=brush._fx
	End Method

	Method HasAlpha()
		If _a<>1 Or _fx&FX_ALPHABLEND Or _blend = BLEND_MULTIPLY Or _blend = BLEND_ADD Return True
		If _texture[0] <> Null If _texture[0]._flags&TEXTURE_ALPHA Return True
	End Method

	Method GetName$()
		Return _name
	End Method
	Method SetName(name$)
		_name=name
	End Method

	Method GetColor(red Var,green Var,blue Var)
		red=_r*255.0;green=_g*255.0;blue=_b*255.0
	End Method
	Method SetColor(red,green,blue)
		_r=red/255.0;_g=green/255.0;_b=blue/255.0
	End Method

	Method GetAlpha#()
		Return _a
	End Method
	Method SetAlpha(alpha#)
		_a=alpha
	End Method

	Method GetShine#()
		Return _shine
	End Method
	Method SetShine(shine#)
		_shine=shine
	End Method

	Method GetTexture:TTexture(index=0)
		Return _texture[index]
	End Method
	Method SetTexture(texture:TTexture,index=0,frame=0)
		_texture[index]=texture
		_textureframe[index]=frame
	End Method

	Method GetFrame(index=0)
		Return _textureframe[index]
	End Method
	Method SetFrame(frame,index=0)
		_textureframe[index]=frame
	End Method

	Method GetBlend()
		Return _blend
	End Method
	Method SetBlend(blend)
		_blend=blend
	End Method

	Method GetFX()
		Return _fx
	End Method
	Method SetFX(fx)
		_fx=fx
	End Method

	Method GetShader:TShader()
		Return _shader
	End Method
	Method SetShader(shader:TShader)
		_shader=shader
	End Method
End Type

Strict

Import Prime.Max2DEx
Import "texture.bmx"

Const FX_NONE        = 0
Const FX_FULLBRIGHT  = 1
Const FX_VERTEXCOLOR = 2
Const FX_FLATSHADED  = 4
Const FX_NOFOG	       = 8
Const FX_NOCULLING   = 16
Const FX_FORCEALPHA  = 32
Const FX_WIREFRAME   = 64

Type TBrush
	Field _name$
	Field _r#=1.0,_g#=1.0,_b#=1.0,_a#=1.0
	Field _shine#
	Field _texture:TTexture[8],_textureframe[8]
	Field _blend,_fx
	Field _shader:TShader

	Method Copy:TBrush()
		Local newbrush:TBrush=New TBrush
		newbrush.Load Self
		Return newbrush
	End Method

	Method Merge:TBrush(master:TBrush)
		Local red#,green#,blue#,alpha#,shine#,blend,fx,shader:TShader
		red=master._r;green=master._g;blue=master._b;alpha=master._a
		blend=master._blend;fx=master._fx;shader=master._shader

		red:*_r;green:*_g;blue:*_b;alpha:*_a
		Local shine2#=_shine
		If shine=0.0 Then shine=shine2
		If shine<>0.0 And shine2<>0.0 Then shine:*shine2
		If blend=0 Then blend=_blend
		fx:|_fx
		If shader=Null shader=_shader

		Local newbrush:TBrush=New TBrush
		newbrush.SetColor red*255,green*255,blue*255
		newbrush.SetAlpha alpha
		newbrush.SetShine shine
		newbrush.SetBlend blend
		newbrush.SetFX fx
		newbrush.SetShader shader

		For Local i=0 To 7
			newbrush.SetTexture _texture[i],i,_textureframe[i]
			If master._texture[i] newbrush.SetTexture master._texture[i],i,master._textureframe[i]
		Next

		Return newbrush
	End Method

	Method Load(brush:TBrush)
		If brush=Null brush=New TBrush
		_r=brush._r;_g=brush._g;_b=brush._b;_a=brush._a
		_shine=brush._shine
		For Local i=0 To 7
			_texture[i]=brush._texture[i]
			_textureframe[i]=brush._textureframe[i]
		Next
		_blend=brush._blend;_fx=brush._fx
	End Method

	Method HasAlpha()
		If _a<>1 Or _fx&FX_FORCEALPHA Or _blend = BLEND_MULTIPLY Or _blend = BLEND_ADD Return True
		If _texture[0] <> Null If _texture[0]._flags&TEXTURE_ALPHA Return True
	End Method

	Method GetName$()
		Return _name
	End Method
	Method SetName(name$)
		_name=name
	End Method

	Method GetColor(red Var,green Var,blue Var)
		red=_r*255.0;green=_g*255.0;blue=_b*255.0
	End Method
	Method SetColor(red,green,blue)
		_r=red/255.0;_g=green/255.0;_b=blue/255.0
	End Method

	Method GetAlpha#()
		Return _a
	End Method
	Method SetAlpha(alpha#)
		_a=alpha
	End Method

	Method GetShine#()
		Return _shine
	End Method
	Method SetShine(shine#)
		_shine=shine
	End Method

	Method GetTexture:TTexture(index=0)
		Return _texture[index]
	End Method
	Method SetTexture(texture:TTexture,index=0,frame=0)
		_texture[index]=texture
		_textureframe[index]=frame
	End Method

	Method GetFrame(index=0)
		Return _textureframe[index]
	End Method
	Method SetFrame(frame,index=0)
		_textureframe[index]=frame
	End Method

	Method GetBlend()
		Return _blend
	End Method
	Method SetBlend(blend)
		_blend=blend
	End Method

	Method GetFX()
		Return _fx
	End Method
	Method SetFX(fx)
		_fx=fx
	End Method

	Method GetShader:TShader()
		Return _shader
	End Method
	Method SetShader(shader:TShader)
		_shader=shader
	End Method
End Type