To develop a GNet based app is a little bit tricky, because you would need to have a already perfect running "server"-app to test the code of your "client"-app and vice-versa.
So here I show a way to develope both at the same time in step-by-step mode and expand it from day to day.
In general I can say, that it is not a good idea to combine immediately the new GNet-code with your existing 3D-game. Better is to develop the code in an independent app, which is first only PRINT-based. In a second step you can expand it to simple 2D-base. And at last, when all GNet-features are working, you integrate the code into your main game.
GNetTPlayer.gif
GNet can work with two running apps on the same computer or with several computers in your LAN, but also with computers spreaded worldwide. One of them (often the computer which started first) is the "server". He has the job of distributing the datas to all the clients. But you do not need to write any code line related to this. You only start one of the computers as "server" and GNet does the rest automatically under-the-hood. So we can say: The "server" is also a normal client with the same code like he other clients. With the only exception: that he was the one that reached the code line with GNetListen() as first.
Lesson I: A Minimum GNet-Code
How many lines do we need to code a minimalistic server/client-app with both running on the same computer?
SuperStrict
Graphics 400,300
Global Host:TGNetHost=CreateGNetHost()
Local Name:String
If GNetListen(Host,12345) = True
Name="SERVER"
Else
Name="Client"
GNetConnect Host,"127.0.0.1",12345
EndIf
Repeat
GNetSync Host
DrawText "HERE " + Name, 150,10
Flip 1
Until AppTerminate()
CloseGNetHost(Host)
This is all we need. Compile this a run it. Allow windows to open the network for us. You will see your app with the title "SERVER". Move the app window a little bit to the right. Then go to the project folder and start the same EXE for a second time. A second app-window will open and this becomes the "CLIENT", because the "SERVER" already exists.
So we learn,
1.
Every app needs a "Host" with CreateGnetHost() to participate in our GNet.
2.
The first app that calls GNetListen() is the server.
3.
All others, who try it afterwards, will automatically become clients.
4.
They connect to the existing network with GNetConnect().
5.
The continous listening happens in the GNetSync() function
Lesson II: Now lets add some Players
A Player in the GNet needs a GNetObject to communicate. Also the "server" can be used as a client, when he defines his own GNetObject. So the next step needs only a few code lines:
SuperStrict
Graphics 400,300
Global Host:TGNetHost=CreateGNetHost()
Local Name:String
If GNetListen(Host,12345) = True
Name="SERVER"
Else
If GNetConnect(Host,"127.0.0.1",12345)=False
Name ="Error"
Else
Name="Client"
EndIf
EndIf
Global GObj:TGNetObject = CreateGNetObject(Host)
Repeat
Cls
GNetSync(Host)
DrawText "HERE=" + Name, 150,10
ScanGNet
Flip 1
Until AppTerminate()
CloseGNetHost(Host)
Function ScanGnet()
Local i:Int
For Local obj:TGNetObject=EachIn GNetObjects( host, GNET_ALL )
i=i+1
Next
DrawText "Members:" + i , 30,100
End Function
We add our function ScanGNet(), that iterates through all avaiable members. GNetObjects() is a function that returns a list of all members. So we can count the members or ask for their properties via this list.
Start this app (again twice, like descriped in Lesson I) and you will see, that the numbers of members will raise with each new started copy of the app.
Lesson III: Combination with TPlayer
In games we often have a TYPE TPlayer, where you move and interact your players. Here we store are all informations about look, equipment and actions of the players. It would be very helpful if also the GNetObject is a part (a field) of the TPLAYER.
So here we will combine both:
'Global GObj:TGNetObject = CreateGNetObject(Host)
Global Me:TPlayer = TPlayer.AddMe(Name)
Repeat
...
Until....
Type TPlayer
Global All:TList = New TList
Field GObj:TGNetObject
Function AddMe:TPlayer(Name:String)
Local loc:TPlayer = New TPlayer
loc.GObj = CreateGNetObject(Host)
All.AddLast loc
Return loc
End Function
End Type
In a first step we change the connecting to GNet from "before the main loop" to "inside the TPlayer". This function AddMe() create a new TPlayer, connects to the GNet and stores the GObj in a field of the player. At the end we add the player to the list of All players.
We can receive a message about the joining of other players in our ScanGNet() function. Therefore we ask only for new players by using the CONST GNET_CREATED:
Function ScanGnet()
' find new
For Local obj:TGNetObject=EachIn GNetObjects( host, GNET_CREATED )
TPlayer.AddOthers obj
Next
DrawText "TPlayers:" + TPlayer.All.Count() , 30,130
End Function
To add those players to our ALL list, we need a new function inside the TYPE TPlayer:
Type TPlayer
Global All:TList = New TList
Field GNetObj:TGNetObject
...
Function AddOthers(Obj:TGNetObject)
Local loc:TPlayer = New TPlayer
loc.GObj = Obj
All.AddLast loc
End Function
Here is a complete runnable code:
SuperStrict
Graphics 400,300
Global Host:TGNetHost=CreateGNetHost()
Local Name:String
If GNetListen(Host,12345) = True
Name="SERVER"
Else
If GNetConnect(Host,"127.0.0.1",12345)=False
Name ="Error"
Else
Name="Client"
EndIf
EndIf
Global Me:TPlayer = TPlayer.AddMe(Name)
Repeat
Cls
GNetSync(Host)
DrawText "HERE= " + Name, 150,10
ScanGNet
Flip 1
Until AppTerminate()
CloseGNetHost(Host)
Function ScanGnet()
' find new
For Local obj:TGNetObject=EachIn GNetObjects( host, GNET_CREATED )
TPlayer.AddOthers obj
Next
DrawText "TPlayers:" + TPlayer.All.Count() , 30,130
End Function
Type TPlayer
Global All:TList = New TList
Field GObj:TGNetObject
Function AddMe:TPlayer(Name:String)
Local loc:TPlayer = New TPlayer
loc.GObj = CreateGNetObject(Host)
All.AddLast loc
Return loc
End Function
Function AddOthers(Obj:TGNetObject)
Local loc:TPlayer = New TPlayer
loc.GObj = Obj
All.AddLast loc
End Function
End Type
Lesson IV: Inform the others about player properties
Now we are closed to the final step. All properties, movements and states of our player need to be sent to the GNET to inform others about us:
Normally this is done like this:
Global Name:String = "Peter"
Global X:Int = 200
Global Y:Int = 100
CONST GNET_NAME = 1
CONST GNET_X = 2
CONST GNET_Y = 3
SetGNetString GObj, GNET_NAME, Name
SetGNetInt GObj, GNET_X , X
SetGNetInt GObj, GNET_Y , Y
...
DrawText Name, X, Y
Here we have three variables. To transfer them via GNet we define 3 symbolic CONSTANTs for better reading the code. In GNet you can use upto 32 channels (SetGNet...() GetGNet...() )for sharing upto 32 players variables.
In our example we transmit the variables when connecting the player to the GNET:
Type TPlayer
...
Function AddMe:TPlayer(Name:String)
Local loc:TPlayer = New TPlayer
loc.GObj = CreateGNetObject(Host)
SetGNetString loc.GObj, GNET_NAME, Name
SetGNetInt loc.GObj, GNET_X , Rand(50,350)
SetGNetInt loc.GObj, GNET_Y , Rand(50,250)
All.AddLast loc
Return loc
End Function
Now GNET knows where our player is and what is his name. Now the others (and me) need only to draw the members of the ALL-list to display the whole scenery:
Type TPlayer
...
Function DrawAll()
For Local loc:TPlayer = EachIn All
loc.Draw
Next
End Function
Method Draw()
DrawText "Player:" + Name() , X() , Y()
End Method
The gimmick is that X Y and Name are no fields but Methods(). So they can be investigated from GNET each time we need them:
Type TPlayer
...
Method Name:String()
Return GetGNetString(GObj,GNET_NAME)
End Method
Method X:Int()
Return GetGNetInt(GObj,GNET_X)
End Method
Method Y:Int()
Return GetGNetInt(GObj,GNET_X)
End Method
This will display all player on their correct position at the screen.
Complete runnable example:
Compile this app, move the app window a little bit to the right and start the app a seond time from the project folder.
SuperStrict
Const GNET_NAME:Int =1
Const GNET_X:Int =2
Const GNET_Y:Int =3
Graphics 400,300
Global Host:TGNetHost=CreateGNetHost()
Local Name:String
If GNetListen(Host,12345) = True
Name="SERVER"
Else
If GNetConnect(Host,"127.0.0.1",12345)=False
Name ="Error"
Else
Name="Client"
EndIf
EndIf
Global Me:TPlayer = TPlayer.AddMe(Name)
Repeat
Cls
GNetSync(Host)
DrawText "HERE=" + Name, 150,10
TPlayer.DrawAll
ScanGNet
Flip 1
Until AppTerminate()
CloseGNetHost(Host)
Function ScanGnet()
' find new
For Local obj:TGNetObject=EachIn GNetObjects( host, GNET_CREATED )
TPlayer.AddOthers obj
Next
DrawText "TPlayers:" + TPlayer.All.Count() , 30,130
End Function
Type TPlayer
Global All:TList = New TList
Field GObj:TGNetObject
Function AddMe:TPlayer(Name:String)
Local loc:TPlayer = New TPlayer
loc.GObj = CreateGNetObject(Host)
SetGNetString loc.GObj, GNET_NAME, Name
SetGNetInt loc.GObj, GNET_X , Rand(50,350)
SetGNetInt loc.GObj, GNET_Y , Rand(50,250)
All.AddLast loc
Return loc
End Function
Function AddOthers(Obj:TGNetObject)
Local loc:TPlayer = New TPlayer
loc.GObj = Obj
All.AddLast loc
End Function
Function DrawAll()
For Local loc:TPlayer = EachIn All
loc.Draw
Next
End Function
Method Draw()
DrawText "Player:" + Name() , X() , Y()
End Method
Method Name:String()
Return GetGNetString(GObj,GNET_NAME)
End Method
Method X:Int()
Return GetGNetInt(GObj,GNET_X)
End Method
Method Y:Int()
Return GetGNetInt(GObj,GNET_Y)
End Method
End Type
Lesson V: Moving the players during the game
So... this all means that we do not need FIELDS to move the players or do action. We simply report new values to the GNET, which publish the informations to all members (also back to me) and the drawing function will draw the scenery depending only on the information it received from the GNET. So you can be sure, that all screens are always in the same state.
Here I show you as a latest step a moving simulation of the players. Depending on the window you have active you can move the player with the Mouse:
Repeat
Cls
GNetSync(Host)
DrawText "HERE=" + Name, 150,10
'****** new line:************
MoveMe
TPlayer.DrawAll
ScanGNet
Flip 1
Until AppTerminate()
Function MoveMe()
If MouseDown(1)
Me.Move MouseX(), MouseY()
EndIf
End Function
Type TPlayer
...
Method Move(X:Int, Y:Int)
SetGNetInt GObj, GNET_X , X
SetGNetInt GObj, GNET_Y , Y
End Method
GNetTPlayer.gif
I could not find a scangnet function in the gnet api documents, is it a blitz runtime library function or is it a user created function?
i dont know. edit: oh you posted the function in a later post. thank you.
X Y Z fields are necessary, to compare the players previous coordinates to calculate the of the distance the player moved to move the entity, at least i believe.. instead of positionentity each time. for MoveEntity. and i think also positionEntity may bypass the b3d collision system. manual collisions may eat up cpu/app resources and lower fps.
You will have X,Y and Z also in a GNet-based game. But not as FIELD but as METHOD. The methods can be used in the same way like the fields, and just as often as you need.
Your actor (player) needs do to exacltly the same processes on all client machines. If you move the player on our computer with MoveEntity(), but on other clients it will be moved with PositionEntity... this will result in different scenes.
Example: On your computer you try to prevent the "falling under the terrain" with MoveEntity(). But on the other clients you use PositionEntiy() to move the Entity? This will result in falling on the other clients!
Quote from: Midimaster on July 16, 2023, 02:44:52Example: On your computer you try to prevent the "falling under the terrain" with MoveEntity(). But on the other clients you use PositionEntiy() to move the Entity? This will result in falling on the other clients!
Doubtful since there shouldn't be any collision/physics on the client side via 'other' players. Positionentity just sets the exact position the 'other' player is at on their app side.
You'll probably want to tween that Position though as it'll look pretty jumpy to other clietns if your just updating with Positionentity() all the time. Perhaps some delta movement would be better.
Lesson VI: Closing Clients and Server
When a client quits he needs to inform GNET that the Object is not longer alive. After this he can close the Host and quit.
On the server-side we need a function to read this "good-bye" messages and we need a reaction in TPlayer, which removes a player from the list.
Client quits
This is how a client says good-bye:
Repeat
....
Until AppTerminate()
CloseGNetObject(Me.GObj)
Delay 500
CloseGNetHost(Host)
End
And the server needs to scan the list of closing clients:
Function ScanGnet()
' find new clients:
For Local obj:TGNetObject=EachIn GNetObjects( host, GNET_CREATED )
TPlayer.AddOthers obj
Next
' find leaving clients:
For Local obj:TGNetObject=EachIn GNetObjects( host, GNET_CLOSED )
TPlayer.Remove obj
Next
End Function
And this is the reaction of TPlayer:
Type TPlayer
Global All:TList = New TList
Field GObj:TGNetObject
...
Function Remove(obj:TGNetObject)
For Local loc:TPlayer = EachIn All
If loc.GObj=obj
All.Remove loc
EndIf
Next
End Function
End Type
Server quits
To find out, when the server quits is more tricky. We use one of the 32 slots to tell the others who the server is. As long as the server is running his "Slot 31" is TRUE. When the servers wants to quit, he closes his GNetObject. This also closes his "Slot 31". The other clients cannot find a Slot 31, which is TRUE and after a waiting period of 1000msec they also quit:
This defines the slot and it's value
Const GNET_SERVER_SLOT:Int =31 ' <----- THIS IS NEW **********
Global I_Am_Server:Int=False ' <----- THIS IS NEW **********
If GNetListen(Host,12345) = True
Name="SERVER"
I_Am_Server = True ' <----- THIS IS NEW **********
Else
....
All members create a GNetObject and define a "Slot 31", but only one of them contains the value TRUE:
Type TPlayer
Global All:TList = New TList
Field GObj:TGNetObject
Function AddMe:TPlayer(Name:String)
Local loc:TPlayer = New TPlayer
loc.GObj = CreateGNetObject(Host)
SetGNetInt(loc.GObj, GNET_SERVER_SLOT, I_Am_Server ) ' <----- THIS IS NEW **********
All.AddLast loc
Return loc
End Function
Now the others listen to this TRUE. Each time they find a "TRUE" slot31 they move the TimeOut into the future:
Function ScanGnet()
...
For Local loc:TGnetObject=EachIn GNetObjects(Host,GNET_ALL )
If GetGNetInt(loc, GNET_SERVER_SLOT)=TRUE
DrawText "Server is Alive",30,160
TimeOut = MilliSecs()+1000
Return
EndIf
Next
End Function
So they TimeOut can never happen as long as the TRUE is found.
But when it happens, the main loop will be left:
Global TimeOut:Int = MilliSecs()+1000
Repeat
Cls
GNetSync(Host)
DrawText "HERE= " + Name, 150,10
ScanGNet
Flip 1
Until AppTerminate() Or TimeOut<MilliSecs() ' <----- THIS IS NEW **********
CloseGNetObject(Me.GObj)
Delay 500
CloseGNetHost(Host)
end
Here is the complete code:
SuperStrict
Graphics 400,300
Global Host:TGNetHost=CreateGNetHost()
Local Name:String
Const GNET_SERVER_SLOT:Int =31
Global I_Am_Server:Int=False
If GNetListen(Host,12345) = True
Name="SERVER"
I_Am_Server = True
Else
If GNetConnect(Host,"127.0.0.1",12345)=False
Name ="Error"
Else
Name="Client"
EndIf
EndIf
Global Me:TPlayer = TPlayer.AddMe(Name)
Global TimeOut:Int = MilliSecs()+1000
Repeat
Cls
GNetSync(Host)
DrawText "HERE= " + Name, 150,10
ScanGNet
Flip 1
Until AppTerminate() Or TimeOut<MilliSecs()
CloseGNetObject(Me.GObj)
Delay 500
CloseGNetHost(Host)
End
Function ScanGnet()
' find new
For Local obj:TGNetObject=EachIn GNetObjects( host, GNET_CREATED )
TPlayer.AddOthers obj
Next
For Local obj:TGNetObject=EachIn GNetObjects( host, GNET_CLOSED )
TPlayer.Remove obj
Next
DrawText "GNetObjects:" + GNetObjects(Host,GNET_ALL ).Count() , 30,100
DrawText "TPlayers:" + TPlayer.All.Count() , 30,130
For Local loc:TGnetObject=EachIn GNetObjects(Host,GNET_ALL )
If GetGNetInt(loc, GNET_SERVER_SLOT)=True
DrawText "Server is Alive",30,160
TimeOut=MilliSecs()+1000
Return
EndIf
Next
End Function
Type TPlayer
Global All:TList = New TList
Field GObj:TGNetObject
Function AddMe:TPlayer(Name:String)
Local loc:TPlayer = New TPlayer
loc.GObj = CreateGNetObject(Host)
SetGNetInt(loc.GObj, GNET_SERVER_SLOT, I_Am_Server )
All.AddLast loc
Return loc
End Function
Function AddOthers(Obj:TGNetObject)
Local loc:TPlayer = New TPlayer
loc.GObj = Obj
All.AddLast loc
End Function
Function Remove(obj:TGNetObject)
For Local loc:TPlayer = EachIn All
If loc.GObj=obj
All.Remove loc
EndIf
Next
End Function
End Type
@Midimaster can you tell me if the server creates a gnet object for some purpose?
The GNetObject which you can create on the "server" is nothing more than another player like on the clients.
The GNetObject on the "server" has NOT more rights than the other client players. And it cannot do typical job we would call "master-jobs": excluding players or manipulate player' properties, etc...
Is that what you asked for?
Quote from: Midimaster on July 24, 2023, 15:00:20The GNetObject which you can create on the "server" is nothing more than another player like on the clients.
The GNetObject on the "server" has NOT more rights than the other client players. And it cannot do typical job we would call "master-jobs": excluding players or manipulate player' properties, etc...
Is that what you asked for?
yes i do not know of a particular example i had thought a particular use only that say the server crashes or something, a timeout for both client and server.
There is no build-in timeout-feature. As I demonstrated in Lesson VI you can add a "QUIT"-feature by using one of the 32 SLOTs.
And somebody suggested to add another SLOT, where everybody sends a timestamp every 1sec. If now in one of the GNetObjects the time does not move for a period of 5 seconds, the other can guess, that this user computer has crashed .
Lesson VII
A server and 2 clients enable a 3 players game. Each player gets an ID related to the number of members. The clients are monitoring the number of members and monitoring the existence of the server.
move the mouse in each window and watch how the text are moving in the other windows:
SuperStrict
Graphics 400,300
Global Host:TGNetHost=CreateGNetHost()
Local Name:String
Const GNET_SERVER_SLOT:Int =31
Const GNET_NAME:Int = 1
Const GNET_X:Int = 2
Const GNET_Y:Int = 3
Const GNET_ID:Int = 4
Global I_Am_Server:Int=False
Global IsAliveText:String, TimeRemain:String
If GNetListen(Host,12345) = True
Name="SERVER"
I_Am_Server = True
Else
If GNetConnect(Host,"127.0.0.1",12345)=False
Name ="Error"
Else
Name="Client"
EndIf
EndIf
Global Me:TPlayer = TPlayer.AddMe(Name)
Global TimeOut:Int = MilliSecs()+5000
Repeat
Cls
SetColor 255,255,255
DrawText IsAliveText + TimeRemain , 30,160
ScanGNet
MoveMe
TPlayer.DrawAll
ChangeColor Me.Id()
DrawText "HERE= " + Name, 150, 10
Flip 1
Until AppTerminate() Or TimeOut<MilliSecs()
CloseGNetObject(Me.GObj)
Delay 500
CloseGNetHost(Host)
End
Function ScanGnet()
' find new
GNetSync(Host)
For Local obj:TGNetObject=EachIn GNetObjects( host, GNET_CREATED )
TPlayer.AddOthers obj
Next
For Local obj:TGNetObject=EachIn GNetObjects( host, GNET_CLOSED )
TPlayer.Remove obj
Next
DrawText "GNetObjects:" + GNetObjects(Host,GNET_ALL ).Count() , 30,100
DrawText "TPlayers:" + TPlayer.All.Count() , 30,130
IsAliveText = "Server is dead"
TimeRemain = " (timeout in:" + Int((timeOut-MilliSecs())/1000) + "sec)"
For Local loc:TGnetObject=EachIn GNetObjects(Host,GNET_ALL )
If GetGNetInt(loc, GNET_SERVER_SLOT)=True
IsAliveText="Server is Alive"
TimeOut=MilliSecs()+5000
Return
EndIf
Next
End Function
Function MoveMe()
If MouseDown(1)
Me.Move MouseX(), MouseY()
EndIf
End Function
Type TPlayer
Global All:TList = New TList
Field GObj:TGNetObject
Method Move(X:Int, Y:Int)
SetGNetInt GObj, GNET_X , X
SetGNetInt GObj, GNET_Y , Y
End Method
Function DrawAll()
For Local loc:TPlayer = EachIn All
loc.Draw
Next
End Function
Method Draw()
ChangeColor ID()
DrawText "Player:" + Name() , X() , Y()
End Method
Method Name:String()
Return GetGNetString(GObj,GNET_NAME)
End Method
Method X:Int()
Return GetGNetInt(GObj,GNET_X)
End Method
Method Y:Int()
Return GetGNetInt(GObj,GNET_Y)
End Method
Method ID:Int()
Return GetGNetInt(GObj,GNET_ID)
End Method
Method Define(Nr:Int, Nam:String)
Nam = Nam +" (ID=" +Nr + ")"
SetGNetInt GObj, GNET_ID , Nr
SetGNetString GObj, GNET_NAME , Nam
End Method
Function AddMe:TPlayer(Name:String)
Local loc:TPlayer = New TPlayer
' first fast setup
loc.GObj = CreateGNetObject(Host)
SetGNetInt(loc.GObj, GNET_SERVER_SLOT, I_Am_Server )
loc.Define 0,""
loc.move Rand(300),Rand(250)
All.AddLast loc
' second exact setup
ScanGnet()
Delay 500
ScanGnet()
loc.Define GNetObjects(Host,GNET_ALL ).Count() , Name
Return loc
End Function
Function AddOthers(Obj:TGNetObject)
Local loc:TPlayer = New TPlayer
loc.GObj = Obj
All.AddLast loc
End Function
Function Remove(obj:TGNetObject)
For Local loc:TPlayer = EachIn All
If loc.GObj=obj
All.Remove loc
EndIf
Next
End Function
End Type
Function ChangeColor(ID:Int)
Select ID
Case 0
SetColor 77,77,77
Case 1
SetColor 255,255,0
Case 2
SetColor 0,255,255
Case 3
SetColor 255,0,255
End Select
End Function
Maybe add this:
Graphics 400,300
Local modeSelected:Int = 0
Repeat
Cls
If KeyHit(KEY_C) Then modeSelected = 1
If KeyHit(KEY_S) Then modeSelected = 2
DrawText("Press C for client mode.", 20, 50)
DrawText("Press S for server mode.", 20, 75)
Flip 0
Until modeSelected
...
Global I_Am_Server:Int = 0
if modeSelected = 1 Then I_Am_Server = False
if modeSelected = 2 Then I_Am_Server = True
(or adjust similar)
This allows running the same binary and select client or server on startup
bye
Ron
Is there a clearer way of server timeout like checking if the gnetconnect = true? checking server connection.
My skills in GNET are not very deep, So I can only tell, what is possible with the visible code part of GNet....
Server time out? Who wants to know? The server or the clients?
In your server code the server can report nothing to the others. In my way of writing the server, the server has a GObj too. Means he can report timestamps every xx msec via Slot 32.
On the clients side the clients can see how this timestamp changes all the time. If it does not change any longer, this means a time-out. If the clients cannot find the server's GObj anymore this means the servers has quit or plans to quit. They can react.
If I were you, I would not spend to much time in finding out, why the server sometimes crash.... You and me do not have the deep skills to investigate here. Continue with your game and develope other parts meanwhile. The BlitzMax GitHub team will find a solution someday.
Hello, great thing. Thanks.
greeting