Added TEntity.LineIntersects

Added TEntity.LineIntersects

Strict

Import BRL.LinkedList
Import MaxB3D.Math
Import "brush.bmx"
Import "collision.bmx"
Import "worldconfig.bmx"

Const PICKMODE_OFF			= 0
Const PICKMODE_SPHERE		= 1
Const PICKMODE_POLYGON	= 2
Const PICKMODE_BOX			= 3

Type TEntity
	Field _config:TWorldConfig
	Field _matrix:TMatrix=TMatrix.Identity(), _lockmatrix

	Field _name$
	Field _px#,_py#,_pz#
	Field _rx#,_ry#,_rz#
	Field _sx#,_sy#,_sz#

	Field _brush:TBrush=New TBrush

	Field _parent:TEntity,_childlist:TList=CreateList()
	Field _hidden,_order

	Field _collision:TCollision[]
	Field _oldx#,_oldy#,_oldz#
	Field _radiusx#,_radiusy#
	Field _boxx#,_boxy#,_boxz#,_boxwidth#,_boxheight#,_boxdepth#
	Field _type,_typelink:TLink
	Field _pickmode, _obscurer

	Field _cullradius#

	Field _alphaorder#

	Field _linklist:TList=CreateList()

	Method Init:TEntity(config:TWorldConfig,parent:TEntity)
		_config = config
		SetParent parent,False
		For Local l=EachIn Lists()
			AddLink _config.AddObject(Self,l)
		Next

		SetScale 1,1,1

		Return Self
	End Method

	Method Free()
		For Local child:TEntity=EachIn _childlist
			child.Free()
		Next
		For Local link:TLink=EachIn _linklist
			RemoveLink link
		Next
	End Method

	Method AddLink(link:TLink)
		_linklist.AddLast link
	End Method

	Method Lists[]()
		Return [WORLDLIST_ENTITY]
	End Method

	Method CopyData:TEntity(entity:TEntity)
		_config = entity._config
		Local pickmode, obscurer
		entity.GetPickMode pickmode,obscurer

		SetBrush entity.GetBrush()
		SetName entity.GetName()
		SetPickMode pickmode, obscurer
		SetType entity.GetType()
		Return Self
	End Method

	Method Copy_:TEntity(parent:TEntity=Null)
		Return New Self.CopyData(Self).Init(_config,parent)
	End Method

	Method Copy:TEntity(parent:TEntity=Null) Abstract

	Method AddParent(parent:TEntity)
		_parent=parent
		If parent
			_matrix.Overwrite parent._matrix
			parent._childlist.AddLast Self
		EndIf
	End Method

	Method GetParent:TEntity()
		Return _parent
	End Method
	Method SetParent(parent:TEntity,glob=True)
		Local gpx#,gpy#,gpz#
		Local grx#,gry#,grz#
		Local gsx#,gsy#,gsz#

		GetPosition gpx,gpy,gpz,True
		GetRotation grx,gry,grz,True
		GetScale gsx,gsy,gsz,True

		If _parent
			For Local entity:TEntity=EachIn _parent._childlist
				If entity=Self ListRemove(_parent._childlist,Self)
			Next
			_parent=Null
		EndIf

		_px=gpx;_py=gpy;_pz=gpz
		_rx=grx;_ry=gry;_rz=grz
		_sx=gsx;_sy=gsy;_sz=gsz

		If parent=Null UpdateMatrix(True);Return

		AddParent(parent)
		If glob
			SetPosition(gpx,gpy,gpz,True)
			SetRotation(grx,gry,grz,True)
			SetScale(gsx,gsy,gsz,True)
		Else
			UpdateMatrix(False)
		EndIf
	End Method

	Method CountChildren(recursive=False)
		Local count
		If recursive
			For Local child:TEntity=EachIn _childlist
				count:+child.CountChildren(True)
			Next
		EndIf
		Return _childlist.Count()+count
	End Method

	Method FindChild:TEntity(name$,recursive=False)
		For Local child:TEntity=EachIn _childlist
			If child.GetName()=name Return child
		Next
		If recursive
			For Local child:TEntity=EachIn _childlist
				Local entity:TEntity = child.FindChild(name, True)
				If entity Return entity
			Next
		EndIf
	End Method

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

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

	Method GetColor(red Var,green Var,blue Var)
		Return _brush.GetColor(red,green,blue)
	End Method
	Method SetColor(red,green,blue)
		Return _brush.SetColor(red,green,blue)
	End Method

	Method GetAlpha#()
		Return _brush.GetAlpha()
	End Method
	Method SetAlpha(alpha#)
		_brush.SetAlpha(alpha)
	End Method

	Method GetShine#()
		Return _brush.GetShine()
	End Method
	Method SetShine(shine#)
		_brush.SetShine(shine)
	End Method

	Method GetTexture:TTexture(index)
		Return _brush.GetTexture(index)
	End Method
	Method SetTexture(texture:TTexture,index=0,frame=0)
		Return _brush.SetTexture(texture,index,frame)
	End Method

	Method GetFX()
		Return _brush.GetFX()
	End Method
	Method SetFX(fx)
		Return _brush.SetFX(fx)
	End Method

	Method GetBlend()
		Return _brush.GetBlend()
	End Method
	Method SetBlend(blend)
		Return _brush.SetBlend(blend)
	End Method

	Method GetShader:TShader()
		Return _brush.GetShader()
	End Method
	Method SetShader(shader:TShader)
		Return _brush.SetShader(shader)
	End Method

	Method GetScale(x# Var,y# Var,z# Var,glob=False)
		If glob
			x=_matrix._m[0,0]
			y=_matrix._m[1,1]
			z=_matrix._m[2,2]
		Else
			x=_sx;y=_sy;z=_sz
		EndIf
	End Method
	Method SetScale(x#,y#,z#,glob=False)
		_sx=x;_sy=y;_sz=z
		If glob=True And _parent<>Null
			Local entity:TEntity=Self
			Repeat
				_sx:/entity._parent._sx
				_sy:/entity._parent._sy
				_sz:/entity._parent._sz
				entity=entity._parent
			Until entity._parent=Null
		EndIf
		RefreshMatrix()
	End Method

	Method Transform(matrix:TMatrix,glob=False)
		SetMatrix matrix.Multiply(_matrix),glob
	End Method

	Method Turn(pitch#,yaw#,roll#,glob=False)
		_rx:+pitch;_ry:+yaw;_rz:+roll
		RefreshMatrix()
	End Method

	Method Point(target:Object,roll#=0.0)
		Local x#,y#,z#,tx#,ty#,tz#
		GetPosition x,y,z,True
		GetTargetPosition target,tx,ty,tz

		Local xdiff#=x-tx,ydiff#=y-ty,zdiff#=z-tz

		Local dist22#=Sqr((xdiff*xdiff)+(zdiff*zdiff))
		Local pitch#=ATan2(ydiff,dist22)
		Local yaw#=ATan2(xdiff,-zdiff)

		SetRotation pitch,yaw,roll,True
	End Method

	Method GetDistance#(target:Object)
		Local x#,y#,z#,tx#,ty#,tz#
		GetPosition x,y,z,True
		GetTargetPosition target,tx,ty,tz
		Return Sqr((x-tx)*(x-tx)+(y-ty)*(y-ty)+(z-tz)*(z-tz))
	End Method

	Function GetTargetPosition(target:Object,x# Var,y# Var,z# Var)
		Assert target,"Null target given."
		If TEntity(target) TEntity(target).GetPosition x,y,z,True
		If Float[](target)
			Local coords#[]=Float[](target)
			If coords.length>0 x=coords[0]
			If coords.length>1 y=coords[1]
			If coords.length>2 z=coords[2]
		EndIf
		Assert "Invalid target given."
	End Function

	Method GetRotation(pitch# Var,yaw# Var,roll# Var,glob=False)
		If glob
			_matrix.GetRotation pitch,yaw,roll
			pitch:*-1
			yaw:*-1
		Else
			pitch=_rx;yaw=_ry;roll=_rz
		EndIf
	End Method
	Method SetRotation(pitch#,yaw#,roll#,glob=False)
		_rx=pitch;_ry=yaw;_rz=roll
		If glob And _parent<>Null
			Local rx#,ry#,rz#
			_parent.GetRotation rx,ry,rz
			_rx:+rx;_ry:+ry;_rz:+rz
		EndIf
		RefreshMatrix()
	End Method

	Method GetPosition(x# Var,y# Var,z# Var,glob=False)
		x=_px;y=_py;z=_pz
		If glob _matrix.GetPosition x,y,z
	End Method
	Method SetPosition(x#,y#,z#,glob=False)
		_px=x;_py=y;_pz=z

		If glob And _parent<>Null
			Local px#,py#,pz#
			_parent.GetPosition px,py,pz,True
			_px:-px;_py:-py;_pz:-pz
		EndIf

		RefreshMatrix()
	End Method

	Method Move(x#,y#,z#)
		Local matrix:TMatrix=TMatrix.Identity()
		matrix=TMatrix.YawPitchRoll(-_ry,-_rx,_rz).Multiply(matrix)
		matrix=TMatrix.Translation(x,y,z).Multiply(matrix)

		matrix.GetPosition(x,y,z)
		_px:+x;_py:+y;_pz:+z

		RefreshMatrix()
	End Method

	Method Translate(x#,y#,z#,glob=True)
		If glob And _parent
			Local ax#,ay#,az#
			GetRotation ax,ay,az,True

			Local matrix:TMatrix=TMatrix.Roll(-az)
			matrix=matrix.Multiply(TMatrix.Pitch(-ax))
			matrix=matrix.Multiply(TMatrix.Yaw(-ay))
			matrix=matrix.Multiply(TMatrix.Translation(x,y,z))

			matrix.GetPosition x,y,z
		EndIf

		_px:+x;_py:+y;_pz:+z

		RefreshMatrix()
	End Method

	Method GetVisible()
		Local entity:TEntity=Self
		While entity<>Null
			If entity._hidden=True Return False
			entity=entity._parent
		Wend
		Return True
	End Method
	Method SetVisible(visible)
		_hidden=Not visible
	End Method

	Method GetOrder()
		Return _order
	End Method
	Method SetOrder(order)
		_order=order
	End Method

	Method GetCollisions:TCollision[]()
		Return _collision
	End Method

	Method GetRadius(x# Var,y# Var)
		x=_radiusx;y=_radiusy
	End Method
	Method SetRadius(x#,y#=-1)
		_radiusx=x;_radiusy=x
		If y>-1 _radiusy=y
	End Method

	Method GetBox(x# Var,y# Var,z# Var,width# Var,height# Var,depth# Var)
		x=_boxx;y=_boxy;z=_boxz
		width=_boxwidth;height=_boxheight;depth=_boxdepth
	End Method
	Method SetBox(x#,y#,z#,width#,height#,depth#)
		_boxx=x;_boxy=y;_boxz=z
		_boxwidth=width;_boxheight=height;_boxdepth=depth
	End Method

	Method Reset()
		_collision=Null
		GetPosition _oldx,_oldy,_oldz,True
	End Method

	Method GetType()
		Return _type
	End Method
	Method SetType(typ,recursive=False)
		If _typelink
			_linklist.Remove(_typelink)
			_typelink.Remove()
			_typelink=Null
		EndIf

		If typ>0
			If _config.CollisionType[typ]=Null _config.CollisionType[typ]=CreateList()
			_typelink=_config.CollisionType[typ].AddLast(Self)
			AddLink _typelink
		EndIf

		_type=typ
		GetPosition _oldx,_oldy,_oldz,True

		If recursive
			For Local child:TEntity=EachIn _childlist
				child.SetType typ,True
			Next
		EndIf
	End Method

	Method GetCullRadius#()
		Return Abs(_cullradius)
	End Method
	Method SetCullRadius(radius#)
		_cullradius=-radius
	End Method

	Method GetCullParams(x# Var,y# Var,z# Var,radius# Var)
		GetPosition x,y,z,True
		radius=GetCullRadius()
	End Method

	Method IntersectsLine:TVector(ptA:TVector, ptB:TVector)
		Return Null
	End Method

	Method GetPickMode(mode Var, obscurer Var)
		mode = _pickmode
		obscurer = _obscurer
	End Method
	Method SetPickMode(mode, obscurer=True)
		_pickmode = mode
		_obscurer = obscurer
	End Method

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

	Method GetMatrix:TMatrix(alternate=False,copy=True)
		If copy Return _matrix.Copy()
		Return _matrix
	End Method
	Method SetMatrix(matrix:TMatrix,glob=True)
		Local x#,y#,z#,pitch#,yaw#,roll#,sx#,sy#,sz#
		matrix.GetPosition x,y,z
		matrix.GetRotation pitch,yaw,roll
		matrix.GetScale sx,sy,sz

		LockMatrix
		SetPosition x,y,z,glob
		SetRotation pitch,yaw,roll,glob
		SetScale sx,sy,sz,glob
		UnlockMatrix
	End Method

	Method LockMatrix()
		_lockmatrix=True
	End Method
	Method UnlockMatrix()
		_lockmatrix=False
		RefreshMatrix()
	End Method

	Method RefreshMatrix()
		If _lockmatrix Return
		If _parent<>Null
			_matrix.Overwrite _parent._matrix
			UpdateMatrix False
		Else
			UpdateMatrix True
		EndIf

		For Local child:TEntity=EachIn _childlist
			child.RefreshMatrix()
		Next
	End Method

	Method UpdateMatrix(loadidentity)
		If loadidentity _matrix=TMatrix.Identity()
		_matrix=TMatrix.Translation(_px,_py,_pz).Multiply(_matrix)
		_matrix=TMatrix.YawPitchRoll(-_ry,-_rx,_rz).Multiply(_matrix)
		_matrix=TMatrix.Scale(_sx,_sy,_sz).Multiply(_matrix)
	End Method

	Method ObjectEnumerator:Object()
		Return TChildrenEnumerator.Create(_childlist)
	End Method
End Type

Type TChildrenEnumerator
	Field _children:TEntity[],_index=-1

	Function Create:TChildrenEnumerator(list:TList)
		Local enum:TChildrenEnumerator=New TChildrenEnumerator
		enum._children=TEntity[](list.ToArray())
		Return enum
	End Function

	Method HasNext()
		Return _index>=_children.length-1
	End Method

	Method NextObject:Object()
		_index:+1
		Return _children[_index]
	End Method
End Type

Strict

Import BRL.LinkedList
Import MaxB3D.Math
Import "brush.bmx"
Import "collision.bmx"
Import "worldconfig.bmx"

Const PICKMODE_OFF			= 0
Const PICKMODE_SPHERE		= 1
Const PICKMODE_POLYGON	= 2
Const PICKMODE_BOX			= 3

Type TEntity
	Field _config:TWorldConfig
	Field _matrix:TMatrix=TMatrix.Identity(), _lockmatrix

	Field _name$
	Field _px#,_py#,_pz#
	Field _rx#,_ry#,_rz#
	Field _sx#,_sy#,_sz#

	Field _brush:TBrush=New TBrush

	Field _parent:TEntity,_childlist:TList=CreateList()
	Field _hidden,_order

	Field _collision:TCollision[]
	Field _oldx#,_oldy#,_oldz#
	Field _radiusx#,_radiusy#
	Field _boxx#,_boxy#,_boxz#,_boxwidth#,_boxheight#,_boxdepth#
	Field _type,_typelink:TLink
	Field _pickmode, _obscurer

	Field _cullradius#

	Field _alphaorder#

	Field _linklist:TList=CreateList()

	Method Init:TEntity(config:TWorldConfig,parent:TEntity)
		_config = config
		SetParent parent,False
		For Local l=EachIn Lists()
			AddLink _config.AddObject(Self,l)
		Next

		SetScale 1,1,1

		Return Self
	End Method

	Method Free()
		For Local child:TEntity=EachIn _childlist
			child.Free()
		Next
		For Local link:TLink=EachIn _linklist
			RemoveLink link
		Next
	End Method

	Method AddLink(link:TLink)
		_linklist.AddLast link
	End Method

	Method Lists[]()
		Return [WORLDLIST_ENTITY]
	End Method

	Method CopyData:TEntity(entity:TEntity)
		_config = entity._config
		Local pickmode, obscurer
		entity.GetPickMode pickmode,obscurer

		SetBrush entity.GetBrush()
		SetName entity.GetName()
		SetPickMode pickmode, obscurer
		SetType entity.GetType()
		Return Self
	End Method

	Method Copy_:TEntity(parent:TEntity=Null)
		Return New Self.CopyData(Self).Init(_config,parent)
	End Method

	Method Copy:TEntity(parent:TEntity=Null) Abstract

	Method AddParent(parent:TEntity)
		_parent=parent
		If parent
			_matrix.Overwrite parent._matrix
			parent._childlist.AddLast Self
		EndIf
	End Method

	Method GetParent:TEntity()
		Return _parent
	End Method
	Method SetParent(parent:TEntity,glob=True)
		Local gpx#,gpy#,gpz#
		Local grx#,gry#,grz#
		Local gsx#,gsy#,gsz#

		GetPosition gpx,gpy,gpz,True
		GetRotation grx,gry,grz,True
		GetScale gsx,gsy,gsz,True

		If _parent
			For Local entity:TEntity=EachIn _parent._childlist
				If entity=Self ListRemove(_parent._childlist,Self)
			Next
			_parent=Null
		EndIf

		_px=gpx;_py=gpy;_pz=gpz
		_rx=grx;_ry=gry;_rz=grz
		_sx=gsx;_sy=gsy;_sz=gsz

		If parent=Null UpdateMatrix(True);Return

		AddParent(parent)
		If glob
			SetPosition(gpx,gpy,gpz,True)
			SetRotation(grx,gry,grz,True)
			SetScale(gsx,gsy,gsz,True)
		Else
			UpdateMatrix(False)
		EndIf
	End Method

	Method CountChildren(recursive=False)
		Local count
		If recursive
			For Local child:TEntity=EachIn _childlist
				count:+child.CountChildren(True)
			Next
		EndIf
		Return _childlist.Count()+count
	End Method

	Method FindChild:TEntity(name$,recursive=False)
		For Local child:TEntity=EachIn _childlist
			If child.GetName()=name Return child
		Next
		If recursive
			For Local child:TEntity=EachIn _childlist
				Local entity:TEntity = child.FindChild(name, True)
				If entity Return entity
			Next
		EndIf
	End Method

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

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

	Method GetColor(red Var,green Var,blue Var)
		Return _brush.GetColor(red,green,blue)
	End Method
	Method SetColor(red,green,blue)
		Return _brush.SetColor(red,green,blue)
	End Method

	Method GetAlpha#()
		Return _brush.GetAlpha()
	End Method
	Method SetAlpha(alpha#)
		_brush.SetAlpha(alpha)
	End Method

	Method GetShine#()
		Return _brush.GetShine()
	End Method
	Method SetShine(shine#)
		_brush.SetShine(shine)
	End Method

	Method GetTexture:TTexture(index)
		Return _brush.GetTexture(index)
	End Method
	Method SetTexture(texture:TTexture,index=0,frame=0)
		Return _brush.SetTexture(texture,index,frame)
	End Method

	Method GetFX()
		Return _brush.GetFX()
	End Method
	Method SetFX(fx)
		Return _brush.SetFX(fx)
	End Method

	Method GetBlend()
		Return _brush.GetBlend()
	End Method
	Method SetBlend(blend)
		Return _brush.SetBlend(blend)
	End Method

	Method GetShader:TShader()
		Return _brush.GetShader()
	End Method
	Method SetShader(shader:TShader)
		Return _brush.SetShader(shader)
	End Method

	Method GetScale(x# Var,y# Var,z# Var,glob=False)
		If glob
			x=_matrix._m[0,0]
			y=_matrix._m[1,1]
			z=_matrix._m[2,2]
		Else
			x=_sx;y=_sy;z=_sz
		EndIf
	End Method
	Method SetScale(x#,y#,z#,glob=False)
		_sx=x;_sy=y;_sz=z
		If glob=True And _parent<>Null
			Local entity:TEntity=Self
			Repeat
				_sx:/entity._parent._sx
				_sy:/entity._parent._sy
				_sz:/entity._parent._sz
				entity=entity._parent
			Until entity._parent=Null
		EndIf
		RefreshMatrix()
	End Method

	Method Transform(matrix:TMatrix,glob=False)
		SetMatrix matrix.Multiply(_matrix),glob
	End Method

	Method Turn(pitch#,yaw#,roll#,glob=False)
		_rx:+pitch;_ry:+yaw;_rz:+roll
		RefreshMatrix()
	End Method

	Method Point(target:Object,roll#=0.0)
		Local x#,y#,z#,tx#,ty#,tz#
		GetPosition x,y,z,True
		GetTargetPosition target,tx,ty,tz

		Local xdiff#=x-tx,ydiff#=y-ty,zdiff#=z-tz

		Local dist22#=Sqr((xdiff*xdiff)+(zdiff*zdiff))
		Local pitch#=ATan2(ydiff,dist22)
		Local yaw#=ATan2(xdiff,-zdiff)

		SetRotation pitch,yaw,roll,True
	End Method

	Method GetDistance#(target:Object)
		Local x#,y#,z#,tx#,ty#,tz#
		GetPosition x,y,z,True
		GetTargetPosition target,tx,ty,tz
		Return Sqr((x-tx)*(x-tx)+(y-ty)*(y-ty)+(z-tz)*(z-tz))
	End Method

	Function GetTargetPosition(target:Object,x# Var,y# Var,z# Var)
		Assert target,"Null target given."
		If TEntity(target) TEntity(target).GetPosition x,y,z,True
		If Float[](target)
			Local coords#[]=Float[](target)
			If coords.length>0 x=coords[0]
			If coords.length>1 y=coords[1]
			If coords.length>2 z=coords[2]
		EndIf
		Assert "Invalid target given."
	End Function

	Method GetRotation(pitch# Var,yaw# Var,roll# Var,glob=False)
		If glob
			_matrix.GetRotation pitch,yaw,roll
			pitch:*-1
			yaw:*-1
		Else
			pitch=_rx;yaw=_ry;roll=_rz
		EndIf
	End Method
	Method SetRotation(pitch#,yaw#,roll#,glob=False)
		_rx=pitch;_ry=yaw;_rz=roll
		If glob And _parent<>Null
			Local rx#,ry#,rz#
			_parent.GetRotation rx,ry,rz
			_rx:+rx;_ry:+ry;_rz:+rz
		EndIf
		RefreshMatrix()
	End Method

	Method GetPosition(x# Var,y# Var,z# Var,glob=False)
		x=_px;y=_py;z=_pz
		If glob _matrix.GetPosition x,y,z
	End Method
	Method SetPosition(x#,y#,z#,glob=False)
		_px=x;_py=y;_pz=z

		If glob And _parent<>Null
			Local px#,py#,pz#
			_parent.GetPosition px,py,pz,True
			_px:-px;_py:-py;_pz:-pz
		EndIf

		RefreshMatrix()
	End Method

	Method Move(x#,y#,z#)
		Local matrix:TMatrix=TMatrix.Identity()
		matrix=TMatrix.YawPitchRoll(-_ry,-_rx,_rz).Multiply(matrix)
		matrix=TMatrix.Translation(x,y,z).Multiply(matrix)

		matrix.GetPosition(x,y,z)
		_px:+x;_py:+y;_pz:+z

		RefreshMatrix()
	End Method

	Method Translate(x#,y#,z#,glob=True)
		If glob And _parent
			Local ax#,ay#,az#
			GetRotation ax,ay,az,True

			Local matrix:TMatrix=TMatrix.Roll(-az)
			matrix=matrix.Multiply(TMatrix.Pitch(-ax))
			matrix=matrix.Multiply(TMatrix.Yaw(-ay))
			matrix=matrix.Multiply(TMatrix.Translation(x,y,z))

			matrix.GetPosition x,y,z
		EndIf

		_px:+x;_py:+y;_pz:+z

		RefreshMatrix()
	End Method

	Method GetVisible()
		Local entity:TEntity=Self
		While entity<>Null
			If entity._hidden=True Return False
			entity=entity._parent
		Wend
		Return True
	End Method
	Method SetVisible(visible)
		_hidden=Not visible
	End Method

	Method GetOrder()
		Return _order
	End Method
	Method SetOrder(order)
		_order=order
	End Method

	Method GetCollisions:TCollision[]()
		Return _collision
	End Method

	Method GetRadius(x# Var,y# Var)
		x=_radiusx;y=_radiusy
	End Method
	Method SetRadius(x#,y#=-1)
		_radiusx=x;_radiusy=x
		If y>-1 _radiusy=y
	End Method

	Method GetBox(x# Var,y# Var,z# Var,width# Var,height# Var,depth# Var)
		x=_boxx;y=_boxy;z=_boxz
		width=_boxwidth;height=_boxheight;depth=_boxdepth
	End Method
	Method SetBox(x#,y#,z#,width#,height#,depth#)
		_boxx=x;_boxy=y;_boxz=z
		_boxwidth=width;_boxheight=height;_boxdepth=depth
	End Method

	Method Reset()
		_collision=Null
		GetPosition _oldx,_oldy,_oldz,True
	End Method

	Method GetType()
		Return _type
	End Method
	Method SetType(typ,recursive=False)
		If _typelink
			_linklist.Remove(_typelink)
			_typelink.Remove()
			_typelink=Null
		EndIf

		If typ>0
			If _config.CollisionType[typ]=Null _config.CollisionType[typ]=CreateList()
			_typelink=_config.CollisionType[typ].AddLast(Self)
			AddLink _typelink
		EndIf

		_type=typ
		GetPosition _oldx,_oldy,_oldz,True

		If recursive
			For Local child:TEntity=EachIn _childlist
				child.SetType typ,True
			Next
		EndIf
	End Method

	Method GetCullRadius#()
		Return Abs(_cullradius)
	End Method
	Method SetCullRadius(radius#)
		_cullradius=-radius
	End Method

	Method GetCullParams(x# Var,y# Var,z# Var,radius# Var)
		GetPosition x,y,z,True
		radius=GetCullRadius()
	End Method

	Method GetPickMode(mode Var, obscurer Var)
		mode = _pickmode
		obscurer = _obscurer
	End Method
	Method SetPickMode(mode, obscurer=True)
		_pickmode = mode
		_obscurer = obscurer
	End Method

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

	Method GetMatrix:TMatrix(alternate=False,copy=True)
		If copy Return _matrix.Copy()
		Return _matrix
	End Method
	Method SetMatrix(matrix:TMatrix,glob=True)
		Local x#,y#,z#,pitch#,yaw#,roll#,sx#,sy#,sz#
		matrix.GetPosition x,y,z
		matrix.GetRotation pitch,yaw,roll
		matrix.GetScale sx,sy,sz

		LockMatrix
		SetPosition x,y,z,glob
		SetRotation pitch,yaw,roll,glob
		SetScale sx,sy,sz,glob
		UnlockMatrix
	End Method

	Method LockMatrix()
		_lockmatrix=True
	End Method
	Method UnlockMatrix()
		_lockmatrix=False
		RefreshMatrix()
	End Method

	Method RefreshMatrix()
		If _lockmatrix Return
		If _parent<>Null
			_matrix.Overwrite _parent._matrix
			UpdateMatrix False
		Else
			UpdateMatrix True
		EndIf

		For Local child:TEntity=EachIn _childlist
			child.RefreshMatrix()
		Next
	End Method

	Method UpdateMatrix(loadidentity)
		If loadidentity _matrix=TMatrix.Identity()
		_matrix=TMatrix.Translation(_px,_py,_pz).Multiply(_matrix)
		_matrix=TMatrix.YawPitchRoll(-_ry,-_rx,_rz).Multiply(_matrix)
		_matrix=TMatrix.Scale(_sx,_sy,_sz).Multiply(_matrix)
	End Method

	Method ObjectEnumerator:Object()
		Return TChildrenEnumerator.Create(_childlist)
	End Method
End Type

Type TChildrenEnumerator
	Field _children:TEntity[],_index=-1

	Function Create:TChildrenEnumerator(list:TList)
		Local enum:TChildrenEnumerator=New TChildrenEnumerator
		enum._children=TEntity[](list.ToArray())
		Return enum
	End Function

	Method HasNext()
		Return _index>=_children.length-1
	End Method

	Method NextObject:Object()
		_index:+1
		Return _children[_index]
	End Method
End Type


Refresh the GUI when mxLoggerObserverGUI updates text of a TGadget

refs gh-17

Type mxLoggerObserverGUI

	Field m_output:Object

	Method Reset()
		If TGadget(m_output)
			SetGadgetText(TGadget(m_output), "")
		End If
	End Method

	Method SendMessage:Object(message:Object, context:Object)
		Local msg:String = String(message)
		If String(context) = "error"
			Notify(msg, True)
		Else If TGadget(m_output)
			Local gadget:TGadget = TGadget(m_output)
			SetGadgetText(gadget, GadgetText(gadget) + "~n" + String(message))
			'Refresh GUI
			Driver.Poll
		Else If m_output = Null
			Notify(msg)
		End If
	End Method

End Type

Type mxLoggerObserverGUI

	Field m_output:Object

	Method Reset()
		If TGadget(m_output)
			SetGadgetText(TGadget(m_output), "")
		End If
	End Method

	Method SendMessage:Object(message:Object, context:Object)
		Local msg:String = String(message)
		If String(context) = "error"
			Notify(msg, True)
		Else If TGadget(m_output)
			Local gadget:TGadget = TGadget(m_output)
			SetGadgetText(gadget, GadgetText(gadget) + "~n" + String(message))
		Else If m_output = Null
			Notify(msg)
		End If
	End Method

End Type


Refactored some tests

Refactored some tests

SuperStrict
Framework bah.maxunit
Import htbaapub.zmq

New TTestSuite.run()

Type TZMQ_Test Extends TTest

	Method Setup() {before}
	End Method

	Method Teardown() {after}
	End Method

	Method Version() {test}
		Local parts:String[] = TZMQ.Version().Split(".")
		assertTrue(parts.Length = 3, "Version string contains 3 parts")
	End Method

	'Make sure recreating an existing context
	Method ContextReCreate() {test}
		Local ct:TZMQ_Context = New TZMQ_Context.Create()
		Try
			ct = ct.Create()
		Catch ex:String
			assertNotNull(ex, "Recreating context failed properly")
		Catch ex:TZMQ_Exception
			fail(ex.ToString())
		End Try
		ct.Term()
	End Method

	'Make sure a socket can be created
	Method SocketCreate() {test}
		Local ct:TZMQ_Context = New TZMQ_Context.Create()
		Local socket:TZMQ_Socket = ct.socket(ZMQ_PUB)
		assertNotNull(socket, "Created socket")
		assertTrue(socket.socket <> Null, "Created pointer to socket")
		socket.Close()
		ct.Term()
	End Method

	'Make sure created messages contain expected data
	Method MessageCreationIntegrity() {test}
		For Local i:Int = 0 To 1000
			Local text:String = "Message " + i
			Local msg:TZMQ_Message = New TZMQ_Message.Create(text)
			assertEquals(text, msg.ToString(), "Message text match")
			assertEqualsI(text.Length, msg.Size(), "Message size match")
			msg.Close()
		Next
	End Method

	'Make sure exchanged messages aren't corrupt
	Method MessageExchangingIntegrity() {test}
		Local addr:String = "tcp://127.0.0.1:6000"
		Local ct:TZMQ_Context = New TZMQ_Context.Create()
		Local publisher:TZMQ_Socket = ct.socket(ZMQ_PUB)
		Local subscriber:TZMQ_Socket = ct.socket(ZMQ_SUB)

		publisher.Bind(addr)
		subscriber.Connect(addr)
		subscriber.SetSockOpt(ZMQ_SUBSCRIBE, Null)

		For Local i:Int = 0 To 100
			Delay 10
			Local text:String = "Message " + i
			publisher.Send(text)
			Local msg:TZMQ_Message = subscriber.Recv()
			If msg = Null Then msg.Close() ; Continue
			assertEquals(text, msg.ToString(), "Message " + i + " match")
			assertEqualsI(text.Length, msg.Size(), "Message " + i + " size match")
			msg.Close()
		Next

		subscriber.Close()
		publisher.Close()
		ct.Term()
	End Method
End Type

SuperStrict
Framework bah.maxunit
Import htbaapub.zmq

New TTestSuite.run()

Type TZMQ_Test Extends TTest

	Method Setup() {before}
	End Method

	Method Teardown() {after}
	End Method

	Method Version() {test}
		Local parts:String[] = TZMQ.Version().Split(".")
		assertTrue(parts.Length = 3, "Version string contains 3 parts")
	End Method

	Method ContextReCreate() {test}
		Local ct:TZMQ_Context = New TZMQ_Context.Create()
		Try
			ct = ct.Create()
		Catch ex:String
			assertNotNull(ex, "Recreating context failed properly")
		Catch ex:TZMQ_Exception
			fail(ex.ToString())
		End Try
		ct.Term()
	End Method

	Method SocketCreate() {test}
		Local ct:TZMQ_Context = New TZMQ_Context.Create()
		Local socket:TZMQ_Socket = ct.socket(ZMQ_PUB)
		assertNotNull(socket, "Created socket")
		socket.Close()
		ct.Term()
	End Method

	Method MessageCreationIntegrity() {test}
		For Local i:Int = 0 To 1000
			Local text:String = "Message " + i
			Local msg:TZMQ_Message = New TZMQ_Message.Create(text)
			assertEquals(text, msg.ToString(), "Message text match")
			assertEqualsI(text.Length, msg.Size(), "Message size match")
			msg.Close()
		Next
	End Method

	Method MessageExchangingIntegrity() {test}
		Local addr:String = "tcp://127.0.0.1:6000"
		Local ct:TZMQ_Context = New TZMQ_Context.Create()
		Local publisher:TZMQ_Socket = ct.socket(ZMQ_PUB)
		Local subscriber:TZMQ_Socket = ct.socket(ZMQ_SUB)

		publisher.Bind(addr)
		subscriber.Connect(addr)
		subscriber.SetSockOpt(ZMQ_SUBSCRIBE, Null)

		For Local i:Int = 0 To 100
			Delay 10
			Local text:String = "Message " + i
			publisher.Send(text)
			Local msg:TZMQ_Message = subscriber.Recv()
			assertEquals(text, msg.ToString(), "Message " + i + " match")
			assertEqualsI(text.Length, msg.Size(), "Message " + i + " size match")
			msg.Close()
		Next

		subscriber.Close()
		publisher.Close()
		ct.Term()
	End Method
End Type


Fixed setting sources url for update implementation

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 Not m_sourcesurl Then m_sourcesurl = mainapp.m_sourcesurl
		If Not 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.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


Small change to default texture flag now that texture filters have been added.

Strict

Import BRL.Pixmap
Import "resource.bmx"

Const TEXTURE_COLOR		= 1
Const TEXTURE_ALPHA		= 2
Const TEXTURE_MASKED	= 4
Const TEXTURE_MIPMAP	= 8
Const TEXTURE_CLAMPU	= 16
Const TEXTURE_CLAMPV	= 32
Const TEXTURE_SPHEREMAP	= 64
Const TEXTURE_CUBEMAP	= 128
Const TEXTURE_VRAM		= 256
Const TEXTURE_HIGHCOLOR	= 512
Const TEXTURE_DEPTH 	= 1024

Const TEXTURE_DEFAULT	= TEXTURE_COLOR
Const TEXTURE_CLAMPUV	= TEXTURE_CLAMPU|TEXTURE_CLAMPV

Const BLEND_NONE      = 0
Const BLEND_ALPHA	     = 1
Const BLEND_MULTIPLY  = 2
Const BLEND_ADD       = 3
Const BLEND_DOT3      = 4
Const BLEND_MULTIPLY2 = 5

Const CUBETEX_LEFT  = 0
Const CUBETEX_FRONT = 1
Const CUBETEX_RIGHT = 2
Const CUBETEX_BACK  = 3
Const CUBETEX_UP    = 4
Const CUBETEX_DOWN  = 5

Type TTexture
	Field _blend=BLEND_MULTIPLY,_coords,_flags
	Field _px#,_py#,_r#,_sx#=1.0,_sy#=1.0
	Field _width=-1,_height=-1,_frame:TTextureFrame[]
	Field _name$

	Method Lock:TPixmap(index=0)
		_frame[index]._locked=True
		Return _frame[index]._pixmap
	End Method

	Method Unlock(index=0)
		_frame[index]._locked=False
		_frame[index]._updateres=True
	End Method

	Method SetPixmap:TPixmap(pixmap:TPixmap,index=0)
		If pixmap=Null _frame[index]=Null;Return
		If _frame[index]=Null _frame[index]=New TTextureFrame
		_frame[index]._pixmap=ConvertPixmap(pixmap,PF_BGRA8888)
		_frame[index]._updateres=True

		If (_width<>-1 Or _height<>-1) And (_width<>PixmapWidth(pixmap) Or _height<>PixmapHeight(pixmap))
			Throw "Pixmap size does not match texture size."
		Else
			_width=PixmapWidth(pixmap)
			_height=PixmapHeight(pixmap)
		EndIf
	End Method

	Method GetFlags()
		Return _flags
	End Method
	Method SetFlags(flags)
		_flags=flags
	End Method

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

	Method GetCoords()
		Return _coords
	End Method
	Method SetCoords(set)
		_coords=set
	End Method

	Method GetScale(x# Var,y# Var)
		x=1.0/_sx;y=1.0/_sy
	End Method
	Method SetScale(x#,y#)
		_sx=1.0/x;_sy=1.0/y
	End Method

	Method GetPosition(x# Var,y# Var)
		x=_px;y=_py
	End Method
	Method SetPosition(x#,y#)
		_px=x;_py=y
	End Method

	Method GetRotation#()
		Return _r
	End Method
	Method SetRotation(angle#)
		_r=angle
	End Method

	Method GetSize(width Var,height Var)
		width=_width;height=_height
	End Method
	Method SetSize(width,height,frames)
		_width=width
		_height=height
		_frame=_frame[..frames]
	End Method

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

Type TTextureFrame
	Field _res:TTextureRes,_updateres
	Field _pixmap:TPixmap,_locked
	Field _buffer:Object
End Type

Type TTextureRes Extends TDriverResource

End Type

Strict

Import BRL.Pixmap
Import "resource.bmx"

Const TEXTURE_COLOR		= 1
Const TEXTURE_ALPHA		= 2
Const TEXTURE_MASKED	= 4
Const TEXTURE_MIPMAP	= 8
Const TEXTURE_CLAMPU	= 16
Const TEXTURE_CLAMPV	= 32
Const TEXTURE_SPHEREMAP	= 64
Const TEXTURE_CUBEMAP	= 128
Const TEXTURE_VRAM		= 256
Const TEXTURE_HIGHCOLOR	= 512
Const TEXTURE_DEPTH 	= 1024

Const TEXTURE_DEFAULT	= TEXTURE_COLOR|TEXTURE_MIPMAP
Const TEXTURE_CLAMPUV	= TEXTURE_CLAMPU|TEXTURE_CLAMPV

Const BLEND_NONE      = 0
Const BLEND_ALPHA	     = 1
Const BLEND_MULTIPLY  = 2
Const BLEND_ADD       = 3
Const BLEND_DOT3      = 4
Const BLEND_MULTIPLY2 = 5

Const CUBETEX_LEFT  = 0
Const CUBETEX_FRONT = 1
Const CUBETEX_RIGHT = 2
Const CUBETEX_BACK  = 3
Const CUBETEX_UP    = 4
Const CUBETEX_DOWN  = 5

Type TTexture
	Field _blend=BLEND_MULTIPLY,_coords,_flags
	Field _px#,_py#,_r#,_sx#=1.0,_sy#=1.0
	Field _width=-1,_height=-1,_frame:TTextureFrame[]
	Field _name$

	Method Lock:TPixmap(index=0)
		_frame[index]._locked=True
		Return _frame[index]._pixmap
	End Method

	Method Unlock(index=0)
		_frame[index]._locked=False
		_frame[index]._updateres=True
	End Method

	Method SetPixmap:TPixmap(pixmap:TPixmap,index=0)
		If pixmap=Null _frame[index]=Null;Return
		If _frame[index]=Null _frame[index]=New TTextureFrame
		_frame[index]._pixmap=ConvertPixmap(pixmap,PF_BGRA8888)
		_frame[index]._updateres=True

		If (_width<>-1 Or _height<>-1) And (_width<>PixmapWidth(pixmap) Or _height<>PixmapHeight(pixmap))
			Throw "Pixmap size does not match texture size."
		Else
			_width=PixmapWidth(pixmap)
			_height=PixmapHeight(pixmap)
		EndIf
	End Method

	Method GetFlags()
		Return _flags
	End Method
	Method SetFlags(flags)
		_flags=flags
	End Method

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

	Method GetCoords()
		Return _coords
	End Method
	Method SetCoords(set)
		_coords=set
	End Method

	Method GetScale(x# Var,y# Var)
		x=1.0/_sx;y=1.0/_sy
	End Method
	Method SetScale(x#,y#)
		_sx=1.0/x;_sy=1.0/y
	End Method

	Method GetPosition(x# Var,y# Var)
		x=_px;y=_py
	End Method
	Method SetPosition(x#,y#)
		_px=x;_py=y
	End Method

	Method GetRotation#()
		Return _r
	End Method
	Method SetRotation(angle#)
		_r=angle
	End Method

	Method GetSize(width Var,height Var)
		width=_width;height=_height
	End Method
	Method SetSize(width,height,frames)
		_width=width
		_height=height
		_frame=_frame[..frames]
	End Method

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

Type TTextureFrame
	Field _res:TTextureRes,_updateres
	Field _pixmap:TPixmap,_locked
	Field _buffer:Object
End Type

Type TTextureRes Extends TDriverResource

End Type


Completed a comment

Completed a comment

SuperStrict
Framework bah.maxunit
Import htbaapub.zmq

New TTestSuite.run()

Type TZMQ_Test Extends TTest

	Method Setup() {before}
	End Method

	Method Teardown() {after}
	End Method

	Method Version() {test}
		Local parts:String[] = TZMQ.Version().Split(".")
		assertTrue(parts.Length = 3, "Version string contains 3 parts")
	End Method

	'Make sure recreating an existing context isn't possible
	Method ContextReCreate() {test}
		Local ct:TZMQ_Context = New TZMQ_Context.Create()
		Try
			ct = ct.Create()
		Catch ex:String
			assertNotNull(ex, "Recreating context failed properly")
		Catch ex:TZMQ_Exception
			fail(ex.ToString())
		End Try
		ct.Term()
	End Method

	'Make sure a socket can be created
	Method SocketCreate() {test}
		Local ct:TZMQ_Context = New TZMQ_Context.Create()
		Local socket:TZMQ_Socket = ct.socket(ZMQ_PUB)
		assertNotNull(socket, "Created socket")
		assertTrue(socket.socket <> Null, "Created pointer to socket")
		socket.Close()
		ct.Term()
	End Method

	'Make sure created messages contain expected data
	Method MessageCreationFromStringIntegrity() {test}
		For Local i:Int = 0 To 1000
			Local text:String = "Message " + i
			Local msg:TZMQ_Message = New TZMQ_Message.CreateFromString(text)
			assertEquals(text, msg.ToString(), "Message text match")
			assertEqualsI(text.Length, msg.Size(), "Message size match")
			msg.Close()
		Next
	End Method

	'Make sure created messages contain expected data
	Method MessageCreationFromPointerIntegrity() {test}
		For Local i:Int = 0 To 1000
			Local text:String = "Message " + i
			Local msg:TZMQ_Message = New TZMQ_Message.Create(text.ToCString(), text.Length)
			assertEquals(text, msg.ToString(), "Message text match")
			assertEqualsI(text.Length, msg.Size(), "Message size match")
			msg.Close()
			assertTrue(msg.content = Null, "Message data freed")
		Next
	End Method

	'Make sure exchanged messages aren't corrupt
	Method MessageExchangingIntegrity() {test}
		Local addr:String = "tcp://127.0.0.1:6000"
		Local ct:TZMQ_Context = New TZMQ_Context.Create()
		Local publisher:TZMQ_Socket = ct.socket(ZMQ_PUB)
		Local subscriber:TZMQ_Socket = ct.socket(ZMQ_SUB)

		publisher.Bind(addr)
		subscriber.Connect(addr)
		subscriber.SetSockOpt(ZMQ_SUBSCRIBE , Null)

		Delay 1000
		For Local i:Int = 0 To 10
			Delay 10
			Local text:String = "Message " + i
			publisher.Send(text)
			Local msg:TZMQ_Message = subscriber.Recv()
			If msg = Null Then Continue
			assertEquals(text, msg.ToString(), "Message " + i + " match")
			assertEqualsI(text.Length, msg.Size(), "Message " + i + " size match")
			msg.Close()
		Next

		subscriber.Close()
		publisher.Close()
		ct.Term()
	End Method
End Type

SuperStrict
Framework bah.maxunit
Import htbaapub.zmq

New TTestSuite.run()

Type TZMQ_Test Extends TTest

	Method Setup() {before}
	End Method

	Method Teardown() {after}
	End Method

	Method Version() {test}
		Local parts:String[] = TZMQ.Version().Split(".")
		assertTrue(parts.Length = 3, "Version string contains 3 parts")
	End Method

	'Make sure recreating an existing context
	Method ContextReCreate() {test}
		Local ct:TZMQ_Context = New TZMQ_Context.Create()
		Try
			ct = ct.Create()
		Catch ex:String
			assertNotNull(ex, "Recreating context failed properly")
		Catch ex:TZMQ_Exception
			fail(ex.ToString())
		End Try
		ct.Term()
	End Method

	'Make sure a socket can be created
	Method SocketCreate() {test}
		Local ct:TZMQ_Context = New TZMQ_Context.Create()
		Local socket:TZMQ_Socket = ct.socket(ZMQ_PUB)
		assertNotNull(socket, "Created socket")
		assertTrue(socket.socket <> Null, "Created pointer to socket")
		socket.Close()
		ct.Term()
	End Method

	'Make sure created messages contain expected data
	Method MessageCreationFromStringIntegrity() {test}
		For Local i:Int = 0 To 1000
			Local text:String = "Message " + i
			Local msg:TZMQ_Message = New TZMQ_Message.CreateFromString(text)
			assertEquals(text, msg.ToString(), "Message text match")
			assertEqualsI(text.Length, msg.Size(), "Message size match")
			msg.Close()
		Next
	End Method

	'Make sure created messages contain expected data
	Method MessageCreationFromPointerIntegrity() {test}
		For Local i:Int = 0 To 1000
			Local text:String = "Message " + i
			Local msg:TZMQ_Message = New TZMQ_Message.Create(text.ToCString(), text.Length)
			assertEquals(text, msg.ToString(), "Message text match")
			assertEqualsI(text.Length, msg.Size(), "Message size match")
			msg.Close()
			assertTrue(msg.content = Null, "Message data freed")
		Next
	End Method

	'Make sure exchanged messages aren't corrupt
	Method MessageExchangingIntegrity() {test}
		Local addr:String = "tcp://127.0.0.1:6000"
		Local ct:TZMQ_Context = New TZMQ_Context.Create()
		Local publisher:TZMQ_Socket = ct.socket(ZMQ_PUB)
		Local subscriber:TZMQ_Socket = ct.socket(ZMQ_SUB)

		publisher.Bind(addr)
		subscriber.Connect(addr)
		subscriber.SetSockOpt(ZMQ_SUBSCRIBE , Null)

		Delay 1000
		For Local i:Int = 0 To 10
			Delay 10
			Local text:String = "Message " + i
			publisher.Send(text)
			Local msg:TZMQ_Message = subscriber.Recv()
			If msg = Null Then Continue
			assertEquals(text, msg.ToString(), "Message " + i + " match")
			assertEqualsI(text.Length, msg.Size(), "Message " + i + " size match")
			msg.Close()
		Next

		subscriber.Close()
		publisher.Close()
		ct.Term()
	End Method
End Type


Fixed up SetMatrix for MonkeyMax

Fixed up SetMatrix for MonkeyMax

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

' ***** 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_SetInputDevice(ginput);
		bb_audio_SetAudioDevice(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 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

	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 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()
		'TO-DO
		Return 800
	EndMethod

	Method Height:Int()
		'TO-DO
		Return 480
	EndMethod

	Method SetScissor:Int(x:Int, y:Int, w:Int, h:Int)
		Return 0
	EndMethod

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

	Method DrawSurface:Int(surface:gxtkSurface,x:Float,y:Float)
		DrawImage(surface.image, x, y, 0)
		Return 0
	EndMethod

	Method DrawSurface2:Int(surface:gxtkSurface,x:Float,y:Float, srcx:Int, srcy:Int, srcw:Int, srch:Int )
		DrawSubImageRect(surface.image, x, y, srcw, srch, srcx, srcy, srcw, srch)
		Return 0
	EndMethod

	Method DrawLine:Int( x1:Float,y1:Float,x2:Float,y2:Float )
		BlitzMaxDrawLine(x1, y1, x2, y2)
		Return 0
	EndMethod

	Method DrawOval:Int( x:Float, y:Float, w:Float, h:Float )
		BlitzMaxDrawOval(x, y, w, h)
		Return 0
	EndMethod

	Method DrawPoly:Int(vertices:Float[])
		BlitzMaxDrawPoly( vertices )
		Return 0
	EndMethod

	Method DrawRect:Int( x:Float, y:Float, w:Float, h:Float )
		BlitzMaxDrawRect(x, y, 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 >= 1 And key <= 3 )
			Return BRL.PolledInput.MouseDown( key )
		EndIf
		Return BRL.PolledInput.KeyDown( key )
	EndMethod

	Method KeyHit:Int( key:Int )
		If( key >= 1 And key <= 3 )
			Return BRL.PolledInput.MouseHit( key )
		EndIf
		Return BRL.PolledInput.KeyHit( key )
	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 0
	EndMethod

	Method TouchY:Float( index: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 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 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 sound:TSound = LoadSound("data/"+path)
		If sound Then
			Local gs:gxtkSample = New gxtkSample
			gs.setSound(sound)
			Return gs
		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_SetInputDevice(ginput);
		bb_audio_SetAudioDevice(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 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

	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 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()
		'TO-DO
		Return 800
	EndMethod

	Method Height:Int()
		'TO-DO
		Return 480
	EndMethod

	Method SetScissor:Int(x:Int, y:Int, w:Int, h:Int)
		Return 0
	EndMethod

	Method SetMatrix:Int(ix:Float,iy:Float,jx:Float,jy:Float,tx:Float,ty:Float)
'		Local sx:Float = Sqr( (ix*ix) + (jx*jx) )
'		Local sy:Float = Sqr( (iy*iy) + (jy*jy) )
'		Local rot:Float = Atan2( jx, ix )
'		SetTransform( rot, sx, sy )
		SetTransform( 0, ix, jy )
		SetOrigin( tx, ty )
		Return 0
	EndMethod

	Method DrawSurface:Int(surface:gxtkSurface,x:Float,y:Float)
		DrawImage(surface.image, x, y, 0)
		Return 0
	EndMethod

	Method DrawSurface2:Int(surface:gxtkSurface,x:Float,y:Float, srcx:Int, srcy:Int, srcw:Int, srch:Int )
		DrawSubImageRect(surface.image, x, y, srcw, srch, srcx, srcy, srcw, srch)
		Return 0
	EndMethod

	Method DrawLine:Int( x1:Float,y1:Float,x2:Float,y2:Float )
		BlitzMaxDrawLine(x1, y1, x2, y2)
		Return 0
	EndMethod

	Method DrawOval:Int( x:Float, y:Float, w:Float, h:Float )
		BlitzMaxDrawOval(x, y, w, h)
		Return 0
	EndMethod

	Method DrawPoly:Int(vertices:Float[])
		BlitzMaxDrawPoly( vertices )
		Return 0
	EndMethod

	Method DrawRect:Int( x:Float, y:Float, w:Float, h:Float )
		BlitzMaxDrawRect(x, y, 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 >= 1 And key <= 3 )
			Return BRL.PolledInput.MouseDown( key )
		EndIf
		Return BRL.PolledInput.KeyDown( key )
	EndMethod

	Method KeyHit:Int( key:Int )
		If( key >= 1 And key <= 3 )
			Return BRL.PolledInput.MouseHit( key )
		EndIf
		Return BRL.PolledInput.KeyHit( key )
	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 0
	EndMethod

	Method TouchY:Float( index: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 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 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 sound:TSound = LoadSound("data/"+path)
		If sound Then
			Local gs:gxtkSample = New gxtkSample
			gs.setSound(sound)
			Return gs
		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 ******


Fixed example1.bmx

Fixed example1.bmx

SuperStrict
Import htbaapub.xmlrpc

For Local a:Int = 0 To 10
	GCSuspend()
	'create a new request object
	Local request:Byte Ptr = XMLRPC_RequestNew()

	'Set the method name and tell it we are making a request
	XMLRPC_RequestSetMethodName(request, String("hello").ToCString())
	XMLRPC_RequestSetRequestType(request, xmlrpc_request_call)

	'tell it to write out xml-rpc
	bmxXMLRPC_RequestSetOutputOptions(request, xmlrpc_version_1_0)

	'Create a parameter list vector
	Local xParamList:Byte Ptr = XMLRPC_CreateVector(Null, xmlrpc_vector_array)
	'Add our name as first param to the parameter list

	XMLRPC_VectorAppendString(xParamList, Null, "Htbaa")
	XMLRPC_VectorAppendBase64(xParamList, Null, "Some Base64 text")
	XMLRPC_VectorAppendBoolean(xParamList, Null, False)
	XMLRPC_VectorAppendBoolean(xParamList, Null, True)
	XMLRPC_VectorAppendDateTime(xParamList, Null, 1238998067)
	XMLRPC_VectorAppendDateTime_ISO8601(xParamList, Null, "2009-04-05T16:27:03")
	XMLRPC_VectorAppendDouble(xParamList, Null, 20.43)
	XMLRPC_VectorAppendInt(xParamList, Null, 19)

	XMLRPC_RequestSetData(request, xParamList)

	Try
		Local xmlMessage:Byte Ptr = XMLRPC_REQUEST_ToXML(request, Null)
		Print convertUTF8toISO8859(xmlMessage)
	Catch ex:Object
		DebugLog "error!"
	End Try

	XMLRPC_RequestFree(request, 1)

	GCResume()
	GCCollect()

	Print "memory allocated: " + GCMemAlloced()

Next

SuperStrict
Import htbaapub.xmlrpc

For Local a:Int = 0 To 10
	GCSuspend()
	'create a new request object
	Local request:Byte Ptr = XMLRPC_RequestNew()

	'Set the method name and tell it we are making a request
	XMLRPC_RequestSetMethodName(request, String("hello").ToCString())
	XMLRPC_RequestSetRequestType(request, xmlrpc_request_call)

	'tell it to write out xml-rpc (default
	Local output:Byte Ptr = XMLRPC_Create_STRUCT_XMLRPC_REQUEST_OUTPUT_OPTIONS(xmlrpc_version_1_0)
	XMLRPC_RequestSetOutputOptions(request, output)

	'Create a parameter list vector
	Local xParamList:Byte Ptr = XMLRPC_CreateVector(Null, xmlrpc_vector_array)
	'Add our name as first param to the parameter list

	XMLRPC_VectorAppendString(xParamList, Null, "Htbaa")
	XMLRPC_VectorAppendBase64(xParamList, Null, "Some Base64 text")
	XMLRPC_VectorAppendBoolean(xParamList, Null, False)
	XMLRPC_VectorAppendBoolean(xParamList, Null, True)
	XMLRPC_VectorAppendDateTime(xParamList, Null, 1238998067)
	XMLRPC_VectorAppendDateTime_ISO8601(xParamList, Null, "2009-04-05T16:27:03")
	XMLRPC_VectorAppendDouble(xParamList, Null, 20.43)
	XMLRPC_VectorAppendInt(xParamList, Null, 19)

	XMLRPC_RequestSetData(request, xParamList)

	Try
		Local xmlMessage:Byte Ptr = XMLRPC_REQUEST_ToXML(request, Null)
		Print convertUTF8toISO8859(xmlMessage)
	Catch ex:Object
		DebugLog "error!"
	End Try

	'DebugLog output
	XMLRPC_RequestFree(request, 1)

	GCResume()
	GCCollect()

	Print "memory allocated: " + GCMemAlloced()

Next


[csv] (v0.6) 	Added RemoveRow and RemoveRecord to dCSVMap, added RemoveRecord to dCSVRow 	Use dStringVariable.Create(, Null) instead of dVariable.RawToVariable(Null) in dCSVRow.InsertRecordsFromString

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: CSV (comma-separated value) reading/writing
End Rem
Module duct.csv

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

ModuleInfo "History: Version 0.6"
ModuleInfo "History: Added RemoveRow and RemoveRecord to dCSVMap, added RemoveRecord to dCSVRow"
ModuleInfo "History: Use dStringVariable.Create(, Null) instead of dVariable.RawToVariable(Null) in dCSVRow.InsertRecordsFromString"
ModuleInfo "History: Version 0.5"
ModuleInfo "History: dCSVMap rewritten, added serialization"
ModuleInfo "History: Added dCSVRow"
ModuleInfo "History: Renaming dCSVMap.GetRecordByPosition to dCSVMap.GetRecordFromPosition"
ModuleInfo "History: Renamed dCSVMap.InsertRecord to dCSVMap.AddRecord"
ModuleInfo "History: Corrected some documentation"
ModuleInfo "History: Renamed dCSVMap.CheckRecordAtPosition to dCSVMap.HasRecord"
ModuleInfo "History: Renamed module from csvreader to csv"
ModuleInfo "History: Version 0.4"
ModuleInfo "History: Fixed documentation, license, examples"
ModuleInfo "History: Renamed TCSVMap to dCSVMap"
ModuleInfo "History: Updated for API change"
ModuleInfo "History: Version 0.3"
ModuleInfo "History: General cleanup"
ModuleInfo "History: Version 0.2"
ModuleInfo "History: Moved all code into the main source"
ModuleInfo "History: Version 0.1"
ModuleInfo "History: Initial release"

Import brl.stream
Import duct.etc
Import duct.variables
Import duct.intmap

Rem
	bbdoc: duct CSV (comma-separated value) map for reading CSV files.
End Rem
Type dCSVMap

	Field m_rows:dIntMap = New dIntMap

	Rem
		bbdoc: Create a new map.
		returns: Itself.
	End Rem
	Method Create:dCSVMap()
		Return Self
	End Method

'#region Record handling

	Rem
		bbdoc: Get the number of rows in the map
		returns: The number of rows in the map.
	End Rem
	Method GetRowCount:Int()
		Return m_rows.Count()
	End Method

	Rem
		bbdoc: Get the number of columns in the header (row -1).
		returns: The number of columns in the header.
	End Rem
	Method GetHeaderCount:Int()
		Local header:dCSVRow = GetRow(-1, False)
		If header
			Return header.GetCount()
		End If
		Return 0
	End Method

	Rem
		bbdoc: Get the number of records in the given row.
		returns: The number of records in the given row.
	End Rem
	Method GetRowRecordCount:Int(index:Int)
		Local row:dCSVRow = GetRow(index, False)
		If row
			Return row.GetCount()
		End If
		Return 0
	End Method

	Rem
		bbdoc: Get the number of records in the map.
		returns: Nothing.
	End Rem
	Method GetRecordCount:Int()
		Local count:Int
		For Local row:dCSVRow = EachIn m_rows
			count:+ row.GetCount()
		Next
		Return count
	End Method

	Rem
		bbdoc: Insert a row.
		returns: True if the row was inserted, or False if it was not (Null row).
	End Rem
	Method InsertRow:Int(row:dCSVRow)
		If row
			m_rows.Insert(row.GetIndex(), row)
			Return True
		End If
		Return False
	End Method

	Rem
		bbdoc: Insert a record.
		returns: True if the record was inserted, or False if it was not.
	End Rem
	Method InsertRecord:Int(record:dCSVRecord, row:Int)
		If record
			GetRow(row, True).InsertRecord(record)
			Return True
		End If
		Return False
	End Method

	Rem
		bbdoc: Remove the row at the given index.
		returns: True if the row was removed, or False if there is no row at the given index.
	End Rem
	Method RemoveRow:Int(index:Int)
		Return m_rows.Remove(index)
	End Method

	Rem
		bbdoc: Remove the record at the given position.
		returns: True if the record was removed, or False if there is no record at the given position.
	End Rem
	Method RemoveRecord:Int(row:Int, column:Int)
		Local trow:dCSVRow = GetRow(row, False)
		If trow
			Return trow.RemoveRecord(column)
		End If
		Return False
	End Method

	Rem
		bbdoc: Get the row at the given index.
		returns: The row at the given index, or Null if the given row could not be found (barring the option below).
		about: If @docreate is True, the row will be created if it was not found (and returned).
	End Rem
	Method GetRow:dCSVRow(index:Int, docreate:Int = False)
		Local row:dCSVRow = dCSVRow(m_rows.ForKey(index))
		If Not row And docreate
			row = New dCSVRow.Create(index)
			InsertRow(row)
		End If
		Return row
	End Method

	Rem
		bbdoc: Get the record at the given position.
		returns: The record at the given position, or Null if the position does not contain a record.
	End Rem
	Method GetRecord:dCSVRecord(row:Int, column:Int)
		Local trow:dCSVRow = GetRow(row)
		If trow
			Return trow.GetRecord(column)
		End If
		Return Null
	End Method

	Rem
		bbdoc: Check if there is a row at the given index.
		returns: True if there is a row at the given index, or False if there is not.
	End Rem
	Method HasRow:Int(index:Int)
		Return m_rows.Contains(index)
	End Method

	Rem
		bbdoc: Check if there is a record at the given position.
		returns: True if there is a record at the given position, or False if there is not.
	End Rem
	Method HasRecord:Int(row:Int, column:Int)
		Local trow:dCSVRow = GetRow(row)
		If trow
			Return trow.HasRecord(column)
		End If
		Return False
	End Method

	Rem
		bbdoc: Clear the map.
		returns: Nothing.
	End Rem
	Method Clear()
		m_rows.Clear()
	End Method

'#end region Record handling

	Rem
		bbdoc: Get the object enumerator (row enumerator) for the map.
		returns: The map's enumerator.
	End Rem
	Method ObjectEnumerator:dIntMapStandardEnum()
		Return m_rows.ObjectEnumerator()
	End Method

	Rem
		bbdoc: Get the reversed object enumerator (row enumerator) for the map.
		returns: The map's reverse enumerator.
	End Rem
	Method ReverseEnumerator:dIntMapReverseEnum()
		Return m_rows.ReverseEnumerator()
	End Method

	Rem
		bbdoc: Deserialize a CSV map from the given file.
		returns: Itself, or Null if either the stream or separator is Null.
		about: @separator is the separator for columns.

		If @setheader is True, the first column encountered will be the header.
	End Rem
	Method DeserializeFromFile:dCSVMap(url:String, separator:String = ",", setheader:Int = True)
		If separator
			Local stream:TStream = ReadStream(url)
			If stream
				Deserialize(stream, separator, setheader)
				stream.Close()
				Return Self
			End If
		End If
		Return Null
	End Method

	Rem
		bbdoc: Deserialize a CSV map from the given stream.
		returns: Itself.
		about: The map will be cleared when this method is called.

		@separator is the separator for columns.

		If @setheader is True, the first column encountered will be the header (retrievable by row index -1).
	End Rem
	Method Deserialize:dCSVMap(stream:TStream, separator:String = ",", setheader:Int = True)
		Clear()
		Local row:Int = setheader And -1 Or 0
		While Not stream.Eof()
			GetRow(row, True).Deserialize(stream, separator)
			row:+1
		End While
		Return Self
	End Method

	Rem
		bbdoc: Serialize the CSV map to the given file.
		returns: True if the map was serialized, or False if it was not (bad url or Null separator).
	End Rem
	Method SerializeToFile:Int(url:String, separator:String, forcequoting:Int = False)
		If separator
			Local stream:TStream = WriteStream(url)
			If stream
				Serialize(stream, separator, forcequoting)
				stream.Close()
				Return True
			End If
		End If
		Return False
	End Method

	Rem
		bbdoc: Serialize the CSV map to the given stream.
		returns: Nothing.
		about: @separator is the separator for columns.

		If @forcequoting is True, all columns will have quotes around them
	End Rem
	Method Serialize(stream:TStream, separator:String = ",", forcequoting:Int = False)
		For Local row:dCSVRow = EachIn m_rows
			row.Serialize(stream, separator, forcequoting)
			stream.WriteString("~n")
		Next
	End Method

End Type

Rem
	bbdoc: duct CSVRow.
End Rem
Type dCSVRow

	Field m_records:dIntMap = New dIntMap
	Field m_index:Int

	Rem
		bbdoc: Create a CSV row.
		returns: Itself.
	End Rem
	Method Create:dCSVRow(index:Int)
		SetIndex(index)
		Return Self
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the row's index.
		returns: Nothing.
	End Rem
	Method SetIndex(index:Int)
		m_index = index
	End Method

	Rem
		bbdoc: Get the index.
		returns: The row's index.
	End Rem
	Method GetIndex:Int()
		Return m_index
	End Method

'#end region Field accessors

'#region Record handling

	Rem
		bbdoc: Insert the given record.
		returns: Nothing.
	End Rem
	Method InsertRecord:Int(record:dCSVRecord)
		If record
			m_records.Insert(record.GetIndex(), record)
			Return True
		End If
		Return False
	End Method

	Rem
		bbdoc: Parse and insert the records from the given string, using the given separator.
		returns: The number of records inserted, or -1 if the given string or separator are Null.
		about: If a newline character (ASCII 10) is encountered the parsing will terminate.
	End Rem
	Method InsertRecordsFromString:Int(line:String, separator:String = ",")
		If line And separator
			Local char:Int, quote:Int, index:Int, issep:Int, buf:dTempBuf = New dTempBuf
			For Local n:Int = 0 Until line.Length
				char = line[n]
				If char = 34
					quote:~1
				End If
				issep = line[n..n + separator.Length] = separator And Not quote
				If Not issep And char <> 34 Then buf.AddChar(char)
				If issep Or n = line.Length - 1 Or char = 10
					Local value:String = buf.AsString().Trim(); buf.Clear()
					'If value
						InsertRecord(New dCSVRecord.Create(index, dVariable.RawToVariable(value)))
					'End If
					'n:+ separator.Length
					index:+ 1
					If issep And n = line.Length - 1
						InsertRecord(New dCSVRecord.Create(index, New dStringVariable.Create(, Null)))
						index:+ 1
					End If
					If char = 10 Then Exit
				End If
			Next
			Return index
		End If
		Return -1
	End Method

	Rem
		bbdoc: Remove the record at the given index.
		returns: True if the record was removed, or False if there is no record at the given index.
	End Rem
	Method RemoveRecord:Int(index:Int)
		Return m_records.Remove(index)
	End Method

	Rem
		bbdoc: Get the record at the given index (column).
		returns: The record at the given index, or Null if there is no record at the given index.
	End Rem
	Method GetRecord:dCSVRecord(index:Int)
		Return dCSVRecord(m_records.ForKey(index))
	End Method

	Rem
		bbdoc: Check if the row has the given record.
		returns: Nothing.
	End Rem
	Method HasRecord:Int(index:Int)
		Return m_records.Contains(index)
	End Method

	Rem
		bbdoc: Get the number of records in the row.
		returns: The number of records in the row.
		about: NOTE: This will count blank records (goes by the last column).
	End Rem
	Method GetCount:Int()
		Local success:Int
		Return m_records.GetLastKey(success) + 1
	End Method

'#end region Record handling

	Rem
		bbdoc: Get the object enumerator (record enumerator) for the map.
		returns: The row's enumerator.
	End Rem
	Method ObjectEnumerator:dIntMapStandardEnum()
		Return m_records.ObjectEnumerator()
	End Method

	Rem
		bbdoc: Get the object enumerator (record enumerator) for the map.
		returns: The row's enumerator.
	End Rem
	Method ReverseEnumerator:dIntMapReverseEnum()
		Return m_records.ReverseEnumerator()
	End Method

	Rem
		bbdoc: Deserialize a CSV row from the given stream.
		returns: Itself.
	End Rem
	Method Deserialize:dCSVRow(stream:TStream, separator:String = ",")
			InsertRecordsFromString(stream.ReadLine(), separator)
	End Method

	Rem
		bbdoc: Serialize the row to the given stream.
		returns: Nothing.
	End Rem
	Method Serialize(stream:TStream, separator:String = ",", forcequoting:Int = False)
		Local lastindex:Int
		For Local record:dCSVRecord = EachIn m_records
			'DebugLog("i, li, sub: " + record.m_index + ", " + lastindex + ", " + (record.m_index - lastindex))
			For Local i:Int = 0 Until record.m_index - lastindex
				stream.WriteString(separator)
			Next
			record.Serialize(stream, separator, forcequoting)
			lastindex = record.m_index
		Next
	End Method

	Rem
		bbdoc: Get the row as a string.
		returns: A string containing all the records in the row, separated by the given separator.
	End Rem
	Method Report:String(separator:String = ",", forcequoting:Int = False)
		Local build:String, lastindex:Int, str:String
		For Local record:dCSVRecord = EachIn m_records
			For Local i:Int = 0 Until record.m_index - lastindex
				build:+ separator
			Next
			str = record.ToString()
			If str
				If str.Contains(" ") Or str.Contains("~t") Or str.Contains(separator) Or forcequoting
					build:+ "~q" + str + "~q"
				Else
					build:+ str
				End If
			Else If forcequoting
				build:+ "~q~q"
			End If
			lastindex = record.m_index
		Next
		Return build
	End Method

End Type

Rem
	bbdoc: duct CSVRecord.
End Rem
Type dCSVRecord

	Field m_index:Int
	Field m_variable:dVariable

	Method New()
	End Method

	Rem
		bbdoc: Create a new record.
		returns: Itself.
	End Rem
	Method Create:dCSVRecord(index:Int, variable:dVariable)
		SetIndex(index)
		SetVariable(variable)
		Return Self
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the index for the record.
		returns: Nothing.
	End Rem
	Method SetIndex(index:Int)
		m_index = index
	End Method

	Rem
		bbdoc: Get the record's index.
		returns: The index for the record.
	End Rem
	Method GetIndex:Int()
		Return m_index
	End Method

	Rem
		bbdoc: Set the variable for the record.
		returns: Nothing.
	End Rem
	Method SetVariable(variable:dVariable)
		m_variable = variable
	End Method

	Rem
		bbdoc: Get the record's variable.
		returns: The variable for the record.
	End Rem
	Method GetVariable:dVariable()
		Return m_variable
	End Method

	Rem
		bbdoc: Get the record's header.
		returns: The header for the record, or Null if the record's variable is Null.
	End Rem
	Method GetHeader:String()
		If m_variable
			Return m_variable.GetName()
		End If
		Return Null
	End Method

'#end region Field accessors

	Rem
		bbdoc: Deserialize a record from the given stream.
		returns: Itself.
	End Rem
	Method Deserialize:dCSVRecord()
	End Method

	Rem
		bbdoc: Serialize the record to the given stream.
		returns: Nothing.
	End Rem
	Method Serialize(stream:TStream, separator:String = ",", forcequoting:Int = False)
		Local str:String = ToString()
		If str
			If str.Contains(" ") Or str.Contains("~t") Or str.Contains(separator) Or forcequoting
				stream.WriteString("~q" + str + "~q")
			Else
				stream.WriteString(str)
			End If
		Else If forcequoting
			stream.WriteString("~q~q")
		End If
	End Method

	Rem
		bbdoc: Get the record's value.
		returns: The record's value.
	End Rem
	Method ToString:String()
		If m_variable Then Return m_variable.ValueAsString() Else Return Null
	End Method

End Type

Type dTempBuf

	Const BUFFERINITIAL_SIZE:Int = 128
	Const BUFFER_MULTIPLIER:Double = 1.75:Double

	Field m_buffer:Short Ptr = Null, m_bufSize:Int = 0, m_bufLen:Int = 0
	Field m_bufS:String = Null

	Method New()
	End Method

	Method Delete() NoDebug
		If m_buffer <> Null
			MemFree(m_buffer)
			m_buffer = Null
		End If
	End Method

	Method Clear()
		m_bufS = Null
		m_bufLen = 0
	End Method

	Method AddChar(char:Int)
		If m_buffer = Null
			m_bufSize = BUFFERINITIAL_SIZE
			m_buffer = Short Ptr(MemAlloc(m_bufSize * 2))
			m_bufLen = 0
		Else If m_bufLen = m_bufSize
			Local newsize:Int = Ceil(m_bufSize * BUFFER_MULTIPLIER)
			If newSize < m_bufLen
				newSize = Ceil(m_bufLen * BUFFER_MULTIPLIER)
			End If
			Local temp:Short Ptr = Short Ptr(MemAlloc(newSize * 2))
			If temp = Null
				Throw("(dTempBuf.AddChar()) Unable To allocate buffer of size " + (newSize * 2) + " bytes")
			End If
			m_bufSize = newSize
			MemCopy(temp, m_buffer, m_bufLen * 2)
			MemFree(m_buffer)
			m_buffer = temp
		End If
		m_buffer[m_bufLen] = char
		m_bufLen:+1
	End Method

	Method AsString:String()
		If m_bufS <> Null And (m_buffer = Null Or m_bufS.Length = m_bufLen)
			Return m_bufS
		End If
		m_bufS = String.FromShorts(m_buffer, m_bufLen)
		Return m_bufS
	End Method

End Type

Rem
Copyright (c) 2010 Tim Howard

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

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

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

SuperStrict

Rem
bbdoc: CSV (comma-separated value) reading/writing
End Rem
Module duct.csv

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

ModuleInfo "History: Version 0.5"
ModuleInfo "History: dCSVMap rewritten, added serialization"
ModuleInfo "History: Added dCSVRow"
ModuleInfo "History: Renaming dCSVMap.GetRecordByPosition to dCSVMap.GetRecordFromPosition"
ModuleInfo "History: Renamed dCSVMap.InsertRecord to dCSVMap.AddRecord"
ModuleInfo "History: Corrected some documentation"
ModuleInfo "History: Renamed dCSVMap.CheckRecordAtPosition to dCSVMap.HasRecord"
ModuleInfo "History: Renamed module from csvreader to csv"
ModuleInfo "History: Version 0.4"
ModuleInfo "History: Fixed documentation, license, examples"
ModuleInfo "History: Renamed TCSVMap to dCSVMap"
ModuleInfo "History: Updated for API change"
ModuleInfo "History: Version 0.3"
ModuleInfo "History: General cleanup"
ModuleInfo "History: Version 0.2"
ModuleInfo "History: Moved all code into the main source"
ModuleInfo "History: Version 0.1"
ModuleInfo "History: Initial release"

Import brl.stream
Import duct.etc
Import duct.variables
Import duct.intmap

Rem
	bbdoc: duct CSV (comma-separated value) map for reading CSV files.
End Rem
Type dCSVMap

	Field m_rows:dIntMap = New dIntMap

	Rem
		bbdoc: Create a new map.
		returns: Itself.
	End Rem
	Method Create:dCSVMap()
		Return Self
	End Method

'#region Record handling

	Rem
		bbdoc: Get the number of rows in the map
		returns: The number of rows in the map.
	End Rem
	Method GetRowCount:Int()
		Return m_rows.Count()
	End Method

	Rem
		bbdoc: Get the number of columns in the header (row -1).
		returns: The number of columns in the header.
	End Rem
	Method GetHeaderCount:Int()
		Local header:dCSVRow = GetRow(-1, False)
		If header
			Return header.GetCount()
		End If
		Return 0
	End Method

	Rem
		bbdoc: Get the number of records in the given row.
		returns: The number of records in the given row.
	End Rem
	Method GetRowRecordCount:Int(index:Int)
		Local row:dCSVRow = GetRow(index, False)
		If row
			Return row.GetCount()
		End If
		Return 0
	End Method

	Rem
		bbdoc: Get the number of records in the map.
		returns: Nothing.
	End Rem
	Method GetRecordCount:Int()
		Local count:Int
		For Local row:dCSVRow = EachIn m_rows
			count:+ row.GetCount()
		Next
		Return count
	End Method

	Rem
		bbdoc: Insert a row.
		returns: True if the row was inserted, or False if it was not (Null row).
	End Rem
	Method InsertRow:Int(row:dCSVRow)
		If row
			m_rows.Insert(row.GetIndex(), row)
			Return True
		End If
		Return False
	End Method

	Rem
		bbdoc: Insert a record.
		returns: True if the record was inserted, or False if it was not.
	End Rem
	Method InsertRecord:Int(record:dCSVRecord, row:Int)
		If record
			GetRow(row, True).InsertRecord(record)
			Return True
		End If
		Return False
	End Method

	Rem
		bbdoc: Get the row at the given index.
		returns: The row at the given index, or Null if the given row could not be found (barring the option below).
		about: If @docreate is True, the row will be created if it was not found (and returned).
	End Rem
	Method GetRow:dCSVRow(index:Int, docreate:Int = False)
		Local row:dCSVRow = dCSVRow(m_rows.ForKey(index))
		If Not row And docreate
			row = New dCSVRow.Create(index)
			InsertRow(row)
		End If
		Return row
	End Method

	Rem
		bbdoc: Get the record at the given position.
		returns: The record at the given position, or Null if the position does not contain a record.
	End Rem
	Method GetRecord:dCSVRecord(row:Int, column:Int)
		Local trow:dCSVRow = GetRow(row)
		If trow
			Return trow.GetRecord(column)
		End If
		Return Null
	End Method

	Rem
		bbdoc: Check if there is a row at the given index.
		returns: True if there is a row at the given index, or False if there is not.
	End Rem
	Method HasRow:Int(index:Int)
		Return m_rows.Contains(index)
	End Method

	Rem
		bbdoc: Check if there is a record at the given position.
		returns: True if there is a record at the given position, or False if there is not.
	End Rem
	Method HasRecord:Int(row:Int, column:Int)
		Local trow:dCSVRow = GetRow(row)
		If trow
			Return trow.HasRecord(column)
		End If
		Return False
	End Method

	Rem
		bbdoc: Clear the map.
		returns: Nothing.
	End Rem
	Method Clear()
		m_rows.Clear()
	End Method

'#end region Record handling

	Rem
		bbdoc: Get the object enumerator (row enumerator) for the map.
		returns: The map's enumerator.
	End Rem
	Method ObjectEnumerator:dIntMapStandardEnum()
		Return m_rows.ObjectEnumerator()
	End Method

	Rem
		bbdoc: Get the reversed object enumerator (row enumerator) for the map.
		returns: The map's reverse enumerator.
	End Rem
	Method ReverseEnumerator:dIntMapReverseEnum()
		Return m_rows.ReverseEnumerator()
	End Method

	Rem
		bbdoc: Deserialize a CSV map from the given file.
		returns: Itself, or Null if either the stream or separator is Null.
		about: @separator is the separator for columns.

		If @setheader is True, the first column encountered will be the header.
	End Rem
	Method DeserializeFromFile:dCSVMap(url:String, separator:String = ",", setheader:Int = True)
		If separator
			Local stream:TStream = ReadStream(url)
			If stream
				Deserialize(stream, separator, setheader)
				stream.Close()
				Return Self
			End If
		End If
		Return Null
	End Method

	Rem
		bbdoc: Deserialize a CSV map from the given stream.
		returns: Itself.
		about: The map will be cleared when this method is called.

		@separator is the separator for columns.

		If @setheader is True, the first column encountered will be the header (retrievable by row index -1).
	End Rem
	Method Deserialize:dCSVMap(stream:TStream, separator:String = ",", setheader:Int = True)
		Clear()
		Local row:Int = setheader And -1 Or 0
		While Not stream.Eof()
			GetRow(row, True).Deserialize(stream, separator)
			row:+1
		End While
		Return Self
	End Method

	Rem
		bbdoc: Serialize the CSV map to the given file.
		returns: True if the map was serialized, or False if it was not (bad url or Null separator).
	End Rem
	Method SerializeToFile:Int(url:String, separator:String, forcequoting:Int = False)
		If separator
			Local stream:TStream = WriteStream(url)
			If stream
				Serialize(stream, separator, forcequoting)
				stream.Close()
				Return True
			End If
		End If
		Return False
	End Method

	Rem
		bbdoc: Serialize the CSV map to the given stream.
		returns: Nothing.
		about: @separator is the separator for columns.

		If @forcequoting is True, all columns will have quotes around them
	End Rem
	Method Serialize(stream:TStream, separator:String = ",", forcequoting:Int = False)
		For Local row:dCSVRow = EachIn m_rows
			row.Serialize(stream, separator, forcequoting)
			stream.WriteString("~n")
		Next
	End Method

End Type

Rem
	bbdoc: duct CSVRow.
End Rem
Type dCSVRow

	Field m_records:dIntMap = New dIntMap
	Field m_index:Int

	Rem
		bbdoc: Create a CSV row.
		returns: Itself.
	End Rem
	Method Create:dCSVRow(index:Int)
		SetIndex(index)
		Return Self
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the row's index.
		returns: Nothing.
	End Rem
	Method SetIndex(index:Int)
		m_index = index
	End Method

	Rem
		bbdoc: Get the index.
		returns: The row's index.
	End Rem
	Method GetIndex:Int()
		Return m_index
	End Method

'#end region Field accessors

'#region Record handling

	Rem
		bbdoc: Insert the given record.
		returns: Nothing.
	End Rem
	Method InsertRecord:Int(record:dCSVRecord)
		If record
			m_records.Insert(record.GetIndex(), record)
			Return True
		End If
		Return False
	End Method

	Rem
		bbdoc: Parse and insert the records from the given string, using the given separator.
		returns: The number of records inserted, or -1 if the given string or separator are Null.
		about: If a newline character (ASCII 10) is encountered the parsing will terminate.
	End Rem
	Method InsertRecordsFromString:Int(line:String, separator:String = ",")
		If line And separator
			Local char:Int, quote:Int, index:Int, issep:Int, buf:dTempBuf = New dTempBuf
			For Local n:Int = 0 Until line.Length
				char = line[n]
				If char = 34
					quote:~1
				End If
				issep = line[n..n + separator.Length] = separator And Not quote
				If Not issep And char <> 34 Then buf.AddChar(char)
				If issep Or n = line.Length - 1 Or char = 10
					Local value:String = buf.AsString().Trim(); buf.Clear()
					'If value
						InsertRecord(New dCSVRecord.Create(index, dVariable.RawToVariable(value)))
					'End If
					'n:+ separator.Length
					index:+ 1
					If issep And n = line.Length - 1
						InsertRecord(New dCSVRecord.Create(index, dVariable.RawToVariable("")))
						index:+ 1
					End If
					If char = 10 Then Exit
				End If
			Next
			Return index
		End If
		Return -1
	End Method

	Rem
		bbdoc: Get the record at the given index (column).
		returns: The record at the given index, or Null if there is no record at the given index.
	End Rem
	Method GetRecord:dCSVRecord(index:Int)
		Return dCSVRecord(m_records.ForKey(index))
	End Method

	Rem
		bbdoc: Check if the row has the given record.
		returns: Nothing.
	End Rem
	Method HasRecord:Int(index:Int)
		Return m_records.Contains(index)
	End Method

	Rem
		bbdoc: Get the number of records in the row.
		returns: The number of records in the row.
		about: NOTE: This will count blank records (goes by the last column).
	End Rem
	Method GetCount:Int()
		Local success:Int
		Return m_records.GetLastKey(success) + 1
	End Method

'#end region Record handling

	Rem
		bbdoc: Get the object enumerator (record enumerator) for the map.
		returns: The row's enumerator.
	End Rem
	Method ObjectEnumerator:dIntMapStandardEnum()
		Return m_records.ObjectEnumerator()
	End Method

	Rem
		bbdoc: Get the object enumerator (record enumerator) for the map.
		returns: The row's enumerator.
	End Rem
	Method ReverseEnumerator:dIntMapReverseEnum()
		Return m_records.ReverseEnumerator()
	End Method

	Rem
		bbdoc: Deserialize a CSV row from the given stream.
		returns: Itself.
	End Rem
	Method Deserialize:dCSVRow(stream:TStream, separator:String = ",")
			InsertRecordsFromString(stream.ReadLine(), separator)
	End Method

	Rem
		bbdoc: Serialize the row to the given stream.
		returns: Nothing.
	End Rem
	Method Serialize(stream:TStream, separator:String = ",", forcequoting:Int = False)
		Local lastindex:Int
		For Local record:dCSVRecord = EachIn m_records
			'DebugLog("i, li, sub: " + record.m_index + ", " + lastindex + ", " + (record.m_index - lastindex))
			For Local i:Int = 0 Until record.m_index - lastindex
				stream.WriteString(separator)
			Next
			record.Serialize(stream, separator, forcequoting)
			lastindex = record.m_index
		Next
	End Method

	Rem
		bbdoc: Get the row as a string.
		returns: A string containing all the records in the row, separated by the given separator.
	End Rem
	Method Report:String(separator:String = ",", forcequoting:Int = False)
		Local build:String, lastindex:Int, str:String
		For Local record:dCSVRecord = EachIn m_records
			For Local i:Int = 0 Until record.m_index - lastindex
				build:+ separator
			Next
			str = record.ToString()
			If str
				If str.Contains(" ") Or str.Contains("~t") Or str.Contains(separator) Or forcequoting
					build:+ "~q" + str + "~q"
				Else
					build:+ str
				End If
			Else If forcequoting
				build:+ "~q~q"
			End If
			lastindex = record.m_index
		Next
		Return build
	End Method

End Type

Rem
	bbdoc: duct CSVRecord.
End Rem
Type dCSVRecord

	Field m_index:Int
	Field m_variable:dVariable

	Method New()
	End Method

	Rem
		bbdoc: Create a new record.
		returns: Itself.
	End Rem
	Method Create:dCSVRecord(index:Int, variable:dVariable)
		SetIndex(index)
		SetVariable(variable)
		Return Self
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the index for the record.
		returns: Nothing.
	End Rem
	Method SetIndex(index:Int)
		m_index = index
	End Method

	Rem
		bbdoc: Get the record's index.
		returns: The index for the record.
	End Rem
	Method GetIndex:Int()
		Return m_index
	End Method

	Rem
		bbdoc: Set the variable for the record.
		returns: Nothing.
	End Rem
	Method SetVariable(variable:dVariable)
		m_variable = variable
	End Method

	Rem
		bbdoc: Get the record's variable.
		returns: The variable for the record.
	End Rem
	Method GetVariable:dVariable()
		Return m_variable
	End Method

	Rem
		bbdoc: Get the record's header.
		returns: The header for the record, or Null if the record's variable is Null.
	End Rem
	Method GetHeader:String()
		If m_variable
			Return m_variable.GetName()
		End If
		Return Null
	End Method

'#end region Field accessors

	Rem
		bbdoc: Deserialize a record from the given stream.
		returns: Itself.
	End Rem
	Method Deserialize:dCSVRecord()
	End Method

	Rem
		bbdoc: Serialize the record to the given stream.
		returns: Nothing.
	End Rem
	Method Serialize(stream:TStream, separator:String = ",", forcequoting:Int = False)
		Local str:String = ToString()
		If str
			If str.Contains(" ") Or str.Contains("~t") Or str.Contains(separator) Or forcequoting
				stream.WriteString("~q" + str + "~q")
			Else
				stream.WriteString(str)
			End If
		Else If forcequoting
			stream.WriteString("~q~q")
		End If
	End Method

	Rem
		bbdoc: Get the record's value.
		returns: The record's value.
	End Rem
	Method ToString:String()
		If m_variable Then Return m_variable.ValueAsString() Else Return Null
	End Method

End Type

Type dTempBuf

	Const BUFFERINITIAL_SIZE:Int = 128
	Const BUFFER_MULTIPLIER:Double = 1.75:Double

	Field m_buffer:Short Ptr = Null, m_bufSize:Int = 0, m_bufLen:Int = 0
	Field m_bufS:String = Null

	Method New()
	End Method

	Method Delete() NoDebug
		If m_buffer <> Null
			MemFree(m_buffer)
			m_buffer = Null
		End If
	End Method

	Method Clear()
		m_bufS = Null
		m_bufLen = 0
	End Method

	Method AddChar(char:Int)
		If m_buffer = Null
			m_bufSize = BUFFERINITIAL_SIZE
			m_buffer = Short Ptr(MemAlloc(m_bufSize * 2))
			m_bufLen = 0
		Else If m_bufLen = m_bufSize
			Local newsize:Int = Ceil(m_bufSize * BUFFER_MULTIPLIER)
			If newSize < m_bufLen
				newSize = Ceil(m_bufLen * BUFFER_MULTIPLIER)
			End If
			Local temp:Short Ptr = Short Ptr(MemAlloc(newSize * 2))
			If temp = Null
				Throw("(dTempBuf.AddChar()) Unable To allocate buffer of size " + (newSize * 2) + " bytes")
			End If
			m_bufSize = newSize
			MemCopy(temp, m_buffer, m_bufLen * 2)
			MemFree(m_buffer)
			m_buffer = temp
		End If
		m_buffer[m_bufLen] = char
		m_bufLen:+1
	End Method

	Method AsString:String()
		If m_bufS <> Null And (m_buffer = Null Or m_bufS.Length = m_bufLen)
			Return m_bufS
		End If
		m_bufS = String.FromShorts(m_buffer, m_bufLen)
		Return m_bufS
	End Method

End Type