A separate running Thread that are handling Rooms and Users in queue to pick 2 players after client initiates search request
The following is the scenario which I am really concered about and not quite understand wheather or not I am in the right path
lets say Player A and Player B sent search request to Lobby_1 room.
Both User objects are added to ConcurrentLinkedQueue which is
already mapped to Room ( Looby_1 ) so this condition "if (
queueSize >= 2 )" satisfies and its about to call onSearchSuccess
on lobby object sending Player A and Player B as params. Since
this is separate thread running concurrently all the time, Lets say
Player A quits/disconnects just before this function call
"lobby.onSearchSuccess(player1, player2);" since Player A
disconnect is happening before executing onSearchSuccess on Lobby,
Player A recieves onSearchSuccess and wait for the game to begin
forever. If onSearchSuccess execute first and then disconnect, I
could let Player A know that Player B is disconnected
Is adding Room and User objects to Collection objects and handling
them inside a separate Thread is recommended? is there any problem
in doing so ?
I hope I've made my question clear.
How to solve this problem. I don't think introducing synchronization in not the right approach in multiplayer games
public void run() {
if(!isRunning()) {
setRunning(true);
}
while( isRunning() ) {
for(Map.Entry<Room, ConcurrentLinkedQueue<User>> entry : LOBBY_MAP.entrySet()) {
Room room = entry.getKey();
ConcurrentLinkedQueue<User> queue = entry.getValue();
//check queue size
int queueSize = queue.size();
//minimum queue size must be 2 or greater than 2
if ( queueSize >= 2 ) {
logger.debug("QUEUE SIZE: " + queueSize);
User player1 = queue.poll();
User player2 = queue.poll();
//check if both players are not null
if( player1 != null && player2 != null ) {
Lobby lobby = (Lobby) room.getExtension();
lobby.onSearchSuccess(player1, player2);
}
}
}
}
}
public class UserDisconnectHandler extends BaseServerEventHandler {
public void handleServerEvent(ISFSEvent isfse) throws SFSException {
if(isfse.getType() == SFSEventType.USER_DISCONNECT ) {
User user = (User) isfse.getParameter(SFSEventParam.USER);
List<Room> rooms = (List<Room>) isfse.getParameter(SFSEventParam.JOINED_ROOMS);
if( rooms != null ) {
for(Room room : rooms) {
if( !room.isGame() ) {
SearchWorker.getInstance().removeUser(room, user);
//handle users qualified after search success
}
}
}
}
}
}
Why you are using a tread for that ?
I did this thing with requests. I mean , when player A request , a listener in server , add him to room FindMatch , then when player B request , you add him to that room too. but , you must check the room size in each request and find out if size is 2 , then remove them from FindMatch room and join them in game room.
this is how i did it for my game , with this way , you can even create game room for more than 2 players.
Related
I have been trying to spawn gameObject(Specifically Player gameObject) in Server using this player.GetComponent().Spawn() and then tried to change ownership. It didn't works for me. So frustrated. Can anyone help on this.
I want to spawn two different player in server and client. So I tried to spawn all the players in server only and tried to change the ownership using ownerclientId, LocalClientId. Nothing worked. What happens is that it spawns two players in both server and client screen but only server has the ownership.
Code snippet:
private void Start()
{
var playerSelected = GameObject.Find("PlayerSelected");
int selectedPlayer = playerSelected.GetComponent<PlayerSelected>().selectPlayer;
if (NetworkManager.Singleton.IsServer)
{
PlayerSpawner(selectedPlayer, OwnerClientId);
}
else
{
PlayerSpawnerOnServerRpc(selectedPlayer, OwnerClientId);
}
}
[ServerRpc(RequireOwnership = false)]
void PlayerSpawnerOnServerRpc(int selectedPlayer, ulong clientId)
{
PlayerSpawner(selectedPlayer, clientId);
}
void PlayerSpawner(int selectedPlayer, ulong clientId)
{
GameObject player;
switch(selectedPlayer)
{
case 0:
player = Instantiate(player1Prefab, spawnPositionPlayer1);
player.GetComponent<NetworkObject>().Spawn();
player.GetComponent<NetworkObject>().ChangeOwnership(clientId);
break;
case 1:
player = Instantiate(player2Prefab, spawnPositionPlayer2);
player.GetComponent<NetworkObject>().Spawn();
player.GetComponent<NetworkObject>().ChangeOwnership(clientId);
break;
}
}
The reason this doesn't work is that the server is sending two messages in quick succession.
player.GetComponent<NetworkObject>().Spawn();
player.GetComponent<NetworkObject>().ChangeOwnership(clientId);
The first tells the clients to spawn an object. The second gives ownership to the object. You cannot guarantee (even running locally) that the Spawn() has finished when the ChangeOwnership() is called. So when ChangeOwnership() is called, the client may not have finished initialising the object that's spawned.
Unity were aware of this, and provided you with SpawnWithOwnership (docs). Replace your two lines with one:
player.GetComponent<NetworkObject>().SpawnWithOwnership(clientId);
I am building a OPC UA Client using OPC Foundation SDK. I am able to create a subscription containing some Monitoreditems.
On the OPC UA server these monitored items change value constantly (every second or so).
I want to disconnect the client (simulate a connection broken ), keep the subcription alive and wait for a while. Then I reconnect having my subscriptions back, but I also want all the monitored Item values queued up during the disconnect. Right now I only get the last server value on reconnect.
I am setting a queuesize:
monitoredItem.QueueSize = 100;
To kind of simulate a connection error I have set the "delete subscription" to false on ClosesSession:
m_session.CloseSession(new RequestHeader(), false);
My question is how to capture the content of the queue after a disconnect/connection error???
Should the ‘lost values’ be “new MonitoredItem_Notification” automatically when the client reconnect?
Should the SubscriptionId be the same as before the connection was broken?
Should the sessionId be the same or will a new SessionId let med keep the existing subscriptions? What is the best way to simulate a connection error?
Many questions :-)
A sample from the code where I create the subscription containing some MonitoredItems and the MonitoredItem_Notification event method.
Any OPC UA Guru out there??
if (node.Displayname == "node to monitor")
{
MonitoredItem mon = CreateMonitoredItem((NodeId)node.reference.NodeId, node.Displayname);
m_subscription.AddItem(mon);
m_subscription.ApplyChanges();
}
private MonitoredItem CreateMonitoredItem(NodeId nodeId, string displayName)
{
if (m_subscription == null)
{
m_subscription = new Subscription(m_session.DefaultSubscription);
m_subscription.PublishingEnabled = true;
m_subscription.PublishingInterval = 3000;//1000;
m_subscription.KeepAliveCount = 10;
m_subscription.LifetimeCount = 10;
m_subscription.MaxNotificationsPerPublish = 1000;
m_subscription.Priority = 100;
bool cache = m_subscription.DisableMonitoredItemCache;
m_session.AddSubscription(m_subscription);
m_subscription.Create();
}
// add the new monitored item.
MonitoredItem monitoredItem = new MonitoredItem(m_subscription.DefaultItem);
//Each time a monitored item is sampled, the server evaluates the sample using a filter defined for each monitoreditem.
//The server uses the filter to determine if the sample should be reported. The type of filter is dependent on the type of item.
//DataChangeFilter for Variable, Eventfilter when monitoring Events. etc
//MonitoringFilter f = new MonitoringFilter();
//DataChangeFilter f = new DataChangeFilter();
//f.DeadbandValue
monitoredItem.StartNodeId = nodeId;
monitoredItem.AttributeId = Attributes.Value;
monitoredItem.DisplayName = displayName;
//Disabled, Sampling, (Report (includes sampling))
monitoredItem.MonitoringMode = MonitoringMode.Reporting;
//How often the Client wish to check for new values on the server. Must be 0 if item is an event.
//If a negative number the SamplingInterval is set equal to the PublishingInterval (inherited)
//The Subscriptions KeepAliveCount should always be longer than the SamplingInterval/PublishingInterval
monitoredItem.SamplingInterval = 500;
//Number of samples stored on the server between each reporting
monitoredItem.QueueSize = 100;
monitoredItem.DiscardOldest = true;//Discard oldest values when full
monitoredItem.CacheQueueSize = 100;
monitoredItem.Notification += m_MonitoredItem_Notification;
if (ServiceResult.IsBad(monitoredItem.Status.Error))
{
return null;
}
return monitoredItem;
}
private void MonitoredItem_Notification(MonitoredItem monitoredItem, MonitoredItemNotificationEventArgs e)
{
if (this.InvokeRequired)
{
this.BeginInvoke(new MonitoredItemNotificationEventHandler(MonitoredItem_Notification), monitoredItem, e);
return;
}
try
{
if (m_session == null)
{
return;
}
MonitoredItemNotification notification = e.NotificationValue as MonitoredItemNotification;
if (notification == null)
{
return;
}
string sess = m_session.SessionId.Identifier.ToString();
string s = string.Format(" MonitoredItem: {0}\t Value: {1}\t Status: {2}\t SourceTimeStamp: {3}", monitoredItem.DisplayName, (notification.Value.WrappedValue.ToString().Length == 1) ? notification.Value.WrappedValue.ToString() : notification.Value.WrappedValue.ToString(), notification.Value.StatusCode.ToString(), notification.Value.SourceTimestamp.ToLocalTime().ToString("HH:mm:ss.fff"));
richTextBox1.AppendText(s + "SessionId: " + sess);
}
catch (Exception exception)
{
ClientUtils.HandleException(this.Text, exception);
}
}e here
I don't know how much of this, if any, the SDK you're using does for you, but the approach when reconnecting is generally:
try to resume (re-activate) your old session. If this is successful your subscriptions will already exist and all you need to do is send more PublishRequests. Since you're trying to test by closing the session this probably won't work.
create a new session and then call the TransferSubscription service to transfer the previous subscriptions to your new session. You can then start sending PublishRequests and you'll get the queued notifications.
Again, depending on the stack/SDK/toolkit you're using some or none of this may be handled for you.
I am trying to implement skill based matchmaking using Photon in Unity. It seems
I got most of this code from the documentation and it works but not well.
The problem is that you can't use JoinOrCreate() with the sql lobby type so my logic here is try and find a room, if it fails create one.
void init()
{
_client = Game.Context.GetComponent<SocketConnectionManager>().client;
joinRoom();
}
public void joinRoom()
{
TypedLobby sqlLobby = new TypedLobby("skillLobby", LobbyType.SqlLobby);
string sqlLobbyFilter = "C0 BETWEEN 100 AND 200";
_client.OpJoinRandomRoom(null, MatchMaker.MaxPlayers, MatchmakingMode.FillRoom, sqlLobby, sqlLobbyFilter);
}
public void createRoom()
{
RoomOptions o = new RoomOptions();
o.MaxPlayers = MatchMaker.MaxPlayers;
o.CustomRoomProperties = new Hashtable() { { "C0", Game.Me.getInt("trophies") } };
o.CustomRoomPropertiesForLobby = new string[] { "C0" }; // this makes "C0" available in the lobby
TypedLobby sqlLobby = new TypedLobby("skillLobby", LobbyType.SqlLobby);
_client.OpCreateRoom("", o, sqlLobby);
}
private void onEvent(EventData obj)
{
if (_client.CurrentRoom != null)
{
if (_client.CurrentRoom.PlayerCount >= _client.CurrentRoom.MaxPlayers)
{
// sweet I am good to go.
}
}
else
{
createRoom();
}
}
The problem is this is pretty unreliable. Say two players try to find a game at the same time they will both search fail and then both create. Now I have two players sitting in empty rooms instead of playing each other.
Any ideas on a better system?
Thanks all.
Thank you for choosing Photon!
First of all, there are few things that you should understand about Photon:
you can't use JoinOrCreate() with the sql lobby type
This is not correct.
Where did you read such thing?
Did you test this yourself? What did you test exactly?
onEvent (LoadBalancingClient.OnEventAction) callback cannot be used to be notified of a failed join random room operation. Instead, you should make use of the LoadBalancingClient.OnOpResponseAction callback, as follows:
private void OnOpResponse(OperationResponse operationResponse)
{
switch (operationResponse.Code)
{
case OperationCode.JoinRandomGame:
if (operationResponse.ReturnCode == ErrorCode.NoMatchFound)
{
createRoom();
}
break;
}
}
To detect a join event inside a room (local or remote player entered a room):
private void onEvent(EventData eventData)
{
switch (eventData.Code)
{
case EventCode.Join:
int actorNr = (int)eventData[ParameterCode.ActorNr];
PhotonPlayer originatingPlayer = this.GetPlayerWithId(actorNr);
if (originatingPlayer.IsLocal)
{
}
else
{
}
break;
}
}
To answer your question:
Say two players try to find a game at the same time they will both
search fail and then both create.
Any ideas on a better system?
No.
This issue happens only during the development phase where you use a few clients to run some tests. Once you have enough user base you won't notice this issue.
I am writing an Application that can receive 2 types of event coming into the system from separate sources. I want to have a Context to handle each of them. See the code below :
event MyEvent1{
//stuff for context1
}
event MyEvent2{
//stuff for context2
}
event Cascade{
//PRIORITY stuff for context1 & 2
}
monitor Application{
context parallel1 := context("E1processor");
context parallel2 := context("E2processor");
action onload{
spawn handleE1() to parallel1;
spawn handleE2() to parallel2;
on all MyEvent1() as e {
send e to parallel1;
}
on all MyEvent2() as e {
send e to parallel2;
}
}//onload
action handleE1( ){
on all MyEvent1() as e1 {
//do work, create and route CASCADE event
route Cascade();
//I want to do this!
route Cascade() to parallel2; // < ----- ERROR
}
on all Cascade(){
//URGENT stuff
}
}
action handleE2(){
on all MyEvent2() as e1 {
}
on all Cascade(){
//URGENT stuff
}
}
}//Application
My problem lies with the fact that I want to have the Cascade() event pushed to the front of the processing queue because it is a priority. But when I try to do the following:
//do work, create and route CASCADE event
route Cascade(); //<--- Works
//I want to do this!
route Cascade() to parallel2; // < ----- ERROR
It gives me an error - how can I route the event as a priority from one context to the other?
Unfortunately there's no way to priority send to another context. The answer might be more architectural in nature - can the Cascade handling simply be done in the main context for example?
I'm facing a problem while working with SmartFoxServer 2X that I want to share seeking any help/advice from the community. It might be the default behaviour of SFS, but I want to do it another way for my project. Here's the scenario:
Server Version: 2.0.0-RC1 and I'm using AS3 for client side coding. I'm not doing anything on server side and using basic/default methods of SFS in AS3.
The user logs in a particular Zone and gets a list of rooms available. All the rooms must have a maximum of 2 users. If there is no room, the user creates the room (with settings.maxUsers = 2;) and joins that room. If there is any room, the user checks for a room with room.userCount<2 and joins that room.
If all the rooms are full, the user creates a new room and joins that room, so that another user can log in and join this room. Now, when the first user logs in, a room is created and the user waits for the next user to log in and join that room.
The second user logs in and joins the room created by the first user. Now, when the third user logs in, the second user joins the newly created room while staying in the room created by the first user as well. (NOTE: Only the second user behaves like this; the first user stays in the same room. Similarly when there are 4, 5 and 6 users in the game, the 5th shares the rooms with the 4th and 6th users, in the same fashion as the second user does).
Now there are two rooms. The first room is shared by the first and second users, and the second room is shared by the second and third users. That might be the default way of SFS to handle rooms and users joining them. But I want to keep the first and second players in the first room, even on creation of a new room in the zone, and then create a new room for the third user so that the 4th user can join the room with the third user and so on.
Thanks for having a look into it. Please help.
private function onLogin(event:SFSEvent):void {
SFSVar.removeEventListener(SFSEvent.LOGIN, onLogin);
var count:int = 0;
var roomList:Array = event.currentTarget.roomList;
if(roomList.length==0) {
var my_date:Date = new Date();
var settings:RoomSettings = new RoomSettings("PoolGame"+my_date.fullYear+my_date.month+my_date.date+my_date.hours+my_date.minutes+my_date.seconds);
settings.maxUsers = 2;
settings.groupId = "default";
settings.isGame = true;
SFSVar.send(new CreateRoomRequest(settings, false));
} else if(roomList.length>0) {
for each(var room:Room in roomList) {
count++;
if(room.userCount==2) {
if(count==roomList.length) {
var my_date:Date = new Date();
var settings:RoomSettings = new RoomSettings("PoolGame"+my_date.fullYear+my_date.month+my_date.date+my_date.hours+my_date.minutes+my_date.seconds);
settings.maxUsers = 2;
settings.groupId = "default";
settings.isGame = true;
SFSVar.send(new CreateRoomRequest(settings,false));
break;
}
} else if(room.userCount==1) {
var roomRequest:JoinRoomRequest = new JoinRoomRequest(room.id,null,-1);
SFSVar.send(roomRequest);
break;
}
}
}
}
private function onRoomCreation(event:SFSEvent):void {
SFSVar.removeEventListener(SFSEvent.ROOM_ADD, onRoomCreation);
var room:Room = event.params.room;
var roomRequest:JoinRoomRequest = new JoinRoomRequest(room.id,null,-1);
SFSVar.send(roomRequest);
}
private function onRoomJoin(event:SFSEvent):void {
SFSVar.removeEventListener(SFSEvent.ROOM_JOIN, onRoomJoin);
trace("room joined!!!");
}
I'm sure the original poster figured this out already but for other people reading this, the problem is that the 2nd user still has its room_add event listener. When the 3rd user joins, a new room is created, and because both the 2nd and 3rd users still have their room_add event listeners attached, they both join the new room.
To fix it, either add a line to remove the room_add event listener to the room_join event handler, or add some checking to the room_add event handler to send a room join request only if the player isn't already in a room.