How to prevent user from leaving the current room and join the newly created room? - smartfoxserver

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.

Related

Interpreting server response for events correclty

I would like to store values of event properties received from the server in a database. My problems are that in the event consumer:
I cant figure out which eventtype my client received.
I dont know how to map variant indexes to properties without knowing the EventType.
Events come with the property "EventType", which would solve my first problem. But since I am receiving many different event types, I do not know in which variant index it is located. Should I always relocate "EventType" at index 0 in the select clause whenever creating a new EventFilter?
For the second problem, item.getMonitoringFilter().decode(client.getSerializationContext())) offers a view on the property structure but I am not sure how to use it for mapping of variants to properties. Does anybody know how to solve those problems?
Here is the event consumer code that I use. It is taken from milo client examples.
for (UaMonitoredItem monitoredItem: mItems){
monitoredItem.setEventConsumer((item, vs) -> {
LOGGER.info(
"Event Received from: {}", item.getReadValueId().getNodeId());
LOGGER.info(
"getMonitoredItemId: {}", item.getMonitoredItemId());
LOGGER.info(
"getMonitoringFilter: {}", item.getMonitoringFilter().decode(client.getSerializationContext()));
for (int i = 0; i < vs.length; i++) {
LOGGER.info("variant[{}]:, datatype={}, value={}", i, vs[i].getDataType(), vs[i].getValue());
}
});
}
Thank you in advance.
Update:
Seems I have figured it out, by typcasting to EventFilter. Further information such as qName for event properties or event type node IDs can then be derived:
ExtensionObject eObject = item.getMonitoringFilter();
EventFilter eFilter = ((EventFilter) eObject.decode(client.getSerializationContext()));
QualifiedName qName = eFilter.getSelectClauses()[0].getBrowsePath()[0];
LiteralOperand literalOperand = (LiteralOperand) eFilter.getWhereClause().getElements()[0]
.getFilterOperands()[1].decode(client.getSerializationContext());
NodeId eventTypeNodeId = (NodeId) literalOperand.getValue().getValue();
Didn't you supply the filter in the first place when you created the MonitoredItem? Why do you need to "reverse engineer" the filter result to get back to what you did in the first place?
The properties you receive in the event data and the order they come in are defined by the select clause you used when creating the MonitoredItem. If you choose to select the EventId field then it will always be at the same corresponding index.

Is there a way to start an activity email with Live Data with ViewModel?

I understand that you cannot pass the activity nor the fragment to the view model as parameter and in order to start events or update the UI by click events it is preferable to use LiveData. However, I don't know how to start an email activity with LiveData. As far as I'm concerned you cannot start an activity on the class view model. This is the code that I have. (The lines in comments are just an examples, I know they won't work for this).
MainActivity.kt
val obvserver = Observer<String> {studentEmail.setOnClickListener{ intent = Intent(Intent.ACTION_SEND)
intent.data = Uri.parse("mailto:")
intent.type = "message/rfc822"
intent.putExtra(Intent.EXTRA_EMAIL, selectedStudent.email)
startActivity(Intent.createChooser(intent, "Send Email"))}}
studentEmail.setOnClickListener {
//viewModel.
}
ViewModel.kt
val studentEmail : MutableLiveData<String> by lazy {
MutableLiveData<String>()}

Modifications to data members do not persist after function ends

I've got a simple hierarchy which consists of an Agent that has a ConversationManager con_manager* data member that handles an arbitrary amount of Conversations in a std::vector<Conversation> conversations which themselves contain 1-5 Messages in a std::Vector<Message> messages .
If I start a Conversation via
ConversationManager::startConversation(Message &message) {
Conversation conversation = Conversation(message);
conversations.push_back(conversation)
}
The message is saved properly.
But after adding messages via
int ConversationManager::addMessage(Message &message){
if(conversations.size() > 0){
Conversation conversation = conversations.back();
conversation.addMessage(message);
return 0;
}
return -1;
}
and
int Conversation::addMessage(Message &message){
…
messages.push_back(message);
…
}
subsequent messages besides the first one are not saved. Printing the size and content of messages in Conversation::addMessage(Message &message)shows that my message is saved within that function, but seems to get lost when the scope of the function ends. To my understanding, an explanation would be that the conversation objects used in ConversationManager::addMessage(Message &message) is merely a copy and thus modifications are not saved. But std::Vector::back() returns a reference, so this should be impossible.
I tried only having a single Conversation object without the ConversationManager and then messages are permanently saved. I have a feeling that the Problem lies in my misunderstanding of reference usage. I don't think more code is needed for understanding, but I can provide it if someone thinks it's needed. Any help as to why this occurs would be appreciated, I think I'm making a fundamental mistake here but can't get out of the "This shouldn't be happening"-mindset.
This
Conversation conversation = conversations.back();
creates a copy.
Use
Conversation& conversation = conversations.back();
to use a reference.
This is the problem:
if(conversations.size() > 0){
Conversation conversation = conversations.back();
conversation.addMessage(message);
return 0;
}
The second line in the snippet above makes a copy of a conversation, then adds a message to the copy. Then the recently-modified copy “disappears” when it goes out of scope at the end of the block.
You can create a reference to a conversation, then modify the existing conversation, like this:
if(conversations.size() > 0){
Conversation& conversation = conversations.back(); // note reference
conversation.addMessage(message);
return 0;
}

Smartfoxserver2x handling response synchronization from different Threads

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.

How to delete many-to-many relationship in Entity Framework without loading all of the data

Does anyone know how to delete many-to-many relationship in ADO.NET Entity Framework without having to load all of the data? In my case I have an entity Topic that has a property Subscriptions and I need to remove a single subscription. The code myTopic.Subscriptions.Remove(...) works but I need to load all subscriptions first (e.g. myTopic.Subscriptions.Load()) and I don't want to do that because there are lots (and I mean lots) of subscriptions.
You can Attach() a subscription then Remove() it - note, we're not using Add() here, just Attach, so effectively we're telling EF that we know the object is attached in the store, and asking it to behave as if that were true.
var db = new TopicDBEntities();
var topic = db.Topics.FirstOrDefault(x => x.TopicId == 1);
// Get the subscription you want to delete
var subscription = db.Subscriptions.FirstOrDefault(x => x.SubscriptionId == 2);
topic.Subscriptions.Attach(subscription); // Attach it (the ObjectContext now 'thinks' it belongs to the topic)
topic.Subscriptions.Remove(subscription); // Remove it
db.SaveChanges(); // Flush changes
This whole exchange, including getting the original topic from the database sends these 3 queries to the database:
SELECT TOP (1)
[Extent1].[TopicId] AS [TopicId],
[Extent1].[Description] AS [Description]
FROM [dbo].[Topic] AS [Extent1]
WHERE 1 = [Extent1].[TopicId]
SELECT TOP (1)
[Extent1].[SubscriptionId] AS [SubscriptionId],
[Extent1].[Description] AS [Description]
FROM [dbo].[Subscription] AS [Extent1]
WHERE 2 = [Extent1].[SubscriptionId]
exec sp_executesql N'delete [dbo].[TopicSubscriptions]
where (([TopicId] = #0) and ([SubscriptionId] = #1))',N'#0 int,#1 int',#0=1,#1=2
so it's not pulling all the subscriptions at any point.
This is how to delete without first loading any data. This works in EF5. Not sure about earlier versions.
var db = new TopicDBEntities();
var topic = new Topic { TopicId = 1 };
var subscription = new Subscription { SubscriptionId = 2};
topic.Subscriptions.Add(subscription);
// Attach the topic and subscription as unchanged
// so that they will not be added to the db
// but start tracking changes to the entities
db.Topics.Attach(topic);
// Remove the subscription
// EF will know that the subscription should be removed from the topic
topic.subscriptions.Remove(subscription);
// commit the changes
db.SaveChanges();
One way would be to have a stored proc that will delete your child records directly on the DB and include it in your EF model; then just call it from your DataContext.
Here is my example ...where i know the foreign keys and i don't want to do a db round trip.
I hope this helps someone...
Given:
[client]<--- many-to-many --->[Medication]
Client objClient = new Client() { pkClientID = pkClientID };
EntityKey entityKey = _commonContext.CreateEntityKey("Client", objClient);
objClient.EntityKey = entityKey;
_commonContext.Attach(objClient); //just load entity key ...no db round trip
Medication objMed = new Medication() { pkMedicationID = pkMedicationID };
EntityKey entityKeyMed = _commonContext.CreateEntityKey("Medication", objMed);
objMed.EntityKey = entityKeyMed;
_commonContext.Attach(objMed);
objClient.Medication.Attach(objMed);
objClient.Medication.Remove(objMed); //this deletes
_commonContext.SaveChanges();
If the foreign keys are set, referential integrity should do automatically via the DBMS itself when deleting the parent entities.
If you use code first, as far as I learned in a MVA tutorial, ON DELETE CASCADE is the default behavior set by EF6. If running DB first, you should alter your childtable(s)...
Here is the link: https://mva.microsoft.com/en-US/training-courses/implementing-entity-framework-with-mvc-8931?l=pjxcgEC3_7104984382
In the Video it's mentioned at 20:00 upwards and in the slide presentation it is said on page 14.
Cheers