// In this test, you connect peers in an arbitrary topology.
// Each peer can create/destroy one object and can increase an integer in that object
// Each object is owned by one player
// If the owner disconnects, that object is deleted
// This is a more advanced sample of ReplicaManager. Understand ReplicaManagerCS before looking at this one.
#include "NetworkTypes.h"
#include "StringTable.h"
#include "RakPeerInterface.h"
#include "RakNetworkFactory.h"
#include <stdio.h>
#include <conio.h>
#include <string.h>
#include <stdlib.h>
#include "BitStream.h"
#include "PacketEnumerations.h"
#include "ReplicaManager.h"
#include "Replica.h"
#ifdef _COMPATIBILITY_1
#include "Compatibility1Includes.h" // Developers of a certain platform will know what to do here.
#elif defined(_WIN32)
#include <windows.h> // Sleep
#else
#include <unistd.h> // usleep
#include <cstdio>
#endif
#define MAX_PEERS 8
ReplicaManager replicaManager;
RakPeerInterface *rakPeer;
class TestObject : public Replica
{
public:
TestObject()
{
testInteger=0;
owner=UNASSIGNED_PLAYER_ID;
}
~TestObject()
{
replicaManager.DereferencePointer(this);
}
ReplicaReturnResult SendConstruction( RakNetTime currentTime, PlayerID playerId, RakNet::BitStream *outBitStream, bool *includeTimestamp )
{
// Don't send back to the owner of an object.
// If we didn't prevent then the object would be created on the system that just sent it to us, then back again, forever in a feedback loop.
if (playerId==owner)
return REPLICA_PROCESSING_DONE;
// This string was pre-registered in main with stringTable->AddString so we can send it with the string table and save bandwidth
stringTable->EncodeString("TestObject", 255, outBitStream);
// Write the owner when we construct the object, so we have it right away in order to prevent feedback loops
outBitStream->Write(owner);
return REPLICA_PROCESSING_DONE;
}
void SendDestruction(RakNet::BitStream *outBitStream, PlayerID playerId)
{
// Optional, nothing to send here.
}
ReplicaReturnResult ReceiveDestruction(RakNet::BitStream *inBitStream, PlayerID playerId)
{
printf("Remote object owned by %s:%i destroyed\n", rakPeer->PlayerIDToDottedIP(owner), owner.port);
replicaManager.Destruct(this, playerId, true); // Forward the destruct message to all other systems but the sender
delete this;
return REPLICA_PROCESSING_DONE;
}
ReplicaReturnResult SendScopeChange(bool inScope, RakNet::BitStream *outBitStream, RakNetTime currentTime, PlayerID playerId)
{
outBitStream->Write(inScope);
return REPLICA_PROCESSING_DONE;
}
ReplicaReturnResult ReceiveScopeChange(RakNet::BitStream *inBitStream,PlayerID playerId)
{
return REPLICA_PROCESSING_DONE;
}
ReplicaReturnResult Serialize(bool *sendTimestamp, RakNet::BitStream *outBitStream, RakNetTime lastSendTime, PacketPriority *priority, PacketReliability *reliability, RakNetTime currentTime, PlayerID playerId)
{
// Don't send back to the owner of an object.
if (playerId==owner)
return REPLICA_PROCESSING_DONE;
outBitStream->Write(testInteger);
return REPLICA_PROCESSING_DONE;
}
ReplicaReturnResult Deserialize(RakNet::BitStream *inBitStream, RakNetTime timestamp, RakNetTime lastDeserializeTime, PlayerID playerId )
{
inBitStream->Read(testInteger);
printf("Test integer remotely set to %i on object owned by %s:%i\n", testInteger, rakPeer->PlayerIDToDottedIP(owner), owner.port);
// Forward this event onto everyone but the sender
replicaManager.SignalSerializeNeeded(this, playerId, true);
return REPLICA_PROCESSING_DONE;
}
// Interface from NetworkIDGenerator.
bool IsNetworkIDAuthority(void) const
{
// In peer to peer, everyone is an authority. You must define _P2P_OBJECT_ID for this to work.
return true;
}
// Just an integer I'm synchronizing for testing purposes
int testInteger;
// When this guy disconnects, we delete the object.
PlayerID owner;
};
// This is a required callback I registered to get called when a remote participant calls ReplicaManager::Register on a new object.
// It's equivalent to creating our local copy of an object when a new object is created on a remote system
ReplicaReturnResult ConstructionCB(RakNet::BitStream *inBitStream, RakNetTime timestamp, NetworkID networkID, PlayerID senderId, ReplicaManager *caller)
{
char output[255];
TestObject *testObject;
// I encoded all the data in inBitStream SendConstruction
// I am using the string table to send strings, which is a way to send pre-known strings in a single byte.
// I registered "TestObject" in main().
// The stringTable system has the limitation that all systems must register all the same strings in the same order.
// I could have also used stringCompressor, which would always work but is less efficient to use when we have known strings
stringTable->DecodeString(output, 255, inBitStream);
if (strcmp(output, "TestObject")==0)
{
testObject = new TestObject;
// Construct on all other systems but the system that just sent to us.
// This also automatically references the pointer so we don't need to call replicaManager.ReferencePointer(testObject);
replicaManager.Construct(testObject, false, senderId, true);
// Calling SetNetworkID is important and necessary, because all calls other than construction rely on being able to lookup an object by its network ID
// If we didn't do this, we would never get scope, serialize, or destruction calls because the ReplicaManager wouldn't be able to find this object
testObject->SetNetworkID(networkID);
inBitStream->Read(testObject->owner);
printf("New remote test object created\n");
}
else
{
// Unknown string
assert(0);
}
return REPLICA_PROCESSING_DONE;
}
int main(void)
{
char ch;
char userInput[256];
unsigned int numAddresses, index;
TestObject *t;
rakPeer = RakNetworkFactory::GetRakPeerInterface();
TestObject *localObject=0;
PlayerID playerId;
// This is a global command which will cause sends of NetworkID to use the full PlayerID / localSystemId component.
// This way NetworkIDs can be created locally.
NetworkID::SetPeerToPeerMode(true);
// You have to attach ReplicaManager for it to work, as it is one of the RakNet plugins
rakPeer->AttachPlugin(&replicaManager);
// Anytime we get a new connection, call AddParticipant() on that connection
replicaManager.SetAutoParticipateNewConnections(true);
// Anytime we get a new participant, automatically call Construct() for them with all known objects
replicaManager.SetAutoConstructToNewParticipants(true);
replicaManager.SetReceiveConstructionCB(ConstructionCB);
// Here I use the string table class to efficiently send strings I know in advance.
// The encoding is done in the monster and player SendConstruction functions
// The decoding is done in the ConstructionCB callback
// The stringTable class will also send strings that weren't registered but this just falls back
// to the stringCompressor and wastes 1 extra bit on top of that
stringTable->AddString("TestObject", false); // 2nd parameter of false means a static string so it's not necessary to copy it
// By default all objects are not in scope, meaning we won't serialize the data automatically when they are constructed
// Calling this eliminates the need to call replicaManager.SetScope(this, true, playerId); in Replica::SendConstruction.
replicaManager.SetDefaultScope(true);
// Just start the client, or the server
printf("Demonstration of ReplicaManager for peer to peer\n");
printf("The replica manager provides a framework to make it easier to synchronize\n");
printf("object creation, destruction, and member object updates\n");
printf("Difficulty: Advanced\n\n");
printf("Each peer has one object\n");
printf("Objects updates are chained e.g. A -> B -> C\n");
printf("When a peer disconnects, its object is also destroyed\n");
// Start RakNet
// You can't use 0 because then other systems do not know what port to connect to.
printf("Enter local port (do NOT use 0): ");
gets(userInput);
if (userInput[0]==0)
strcpy(userInput, "60000");
rakPeer->Initialize(MAX_PEERS,atoi(userInput),0,0);
rakPeer->SetMaximumIncomingConnections(MAX_PEERS);
numAddresses=rakPeer->GetNumberOfAddresses();
printf("My IP addresses are:\n");
for (index=0; index < numAddresses; index++)
printf("%i. %s\n", index+1, rakPeer->GetLocalIP(index));
printf("Commands:\n(Q)uit\n(Space) Show status\n(C)onnect to another peer\n(D)isconnect from another peer.\n(T)oggle creation/destruction of local object\n(I)ncrement counter in local object\n");
Packet *p;
while (1)
{
p = rakPeer->Receive();
while (p)
{
if (p->data[0]==ID_DISCONNECTION_NOTIFICATION || p->data[0]==ID_CONNECTION_LOST)
{
printf("Connection lost to %s:%i\n", rakPeer->PlayerIDToDottedIP(p->playerId), p->playerId.port);
// Delete the object owned by this player
for (index=0; index < replicaManager.GetReplicaCount(); index++)
{
t = (TestObject *) replicaManager.GetReplicaAtIndex(index);
if (t->owner==p->playerId)
{
printf("Deleting object owned by that system.\n");
replicaManager.Destruct(t, UNASSIGNED_PLAYER_ID, true); // Send the destruct message to all
delete t;
break;
}
}
}
else if (p->data[0]==ID_NEW_INCOMING_CONNECTION || p->data[0]==ID_CONNECTION_REQUEST_ACCEPTED)
{
printf("New connection from %s:%i\n", rakPeer->PlayerIDToDottedIP(p->playerId), p->playerId.port);
// We need our own unique IP address to go along with the NetworkID. Our externalPlayerID should be unique.
// The internalPlayerID, returned by rakPeer->GetLocalIP, won't be unique if we are behind a NAT machine
if (NetworkIDGenerator::GetExternalPlayerID()==UNASSIGNED_PLAYER_ID)
NetworkIDGenerator::SetExternalPlayerID(rakPeer->GetExternalID(p->playerId));
}
else if (p->data[0]==ID_CONNECTION_ATTEMPT_FAILED)
{
printf("Connection attempt to %s:%i failed.\n", rakPeer->PlayerIDToDottedIP(p->playerId), p->playerId.port);
}
rakPeer->DeallocatePacket(p);
p = rakPeer->Receive();
}
if (kbhit())
{
ch=getch();
if (ch=='q' || ch=='Q')
{
printf("Quitting.\n");
break;
}
else if (ch==' ')
{
printf("\nSTATUS:\n");
if (localObject)
printf("Local object test integer=%i\n", localObject->testInteger);
else
printf("Local object is not instantiated.\n");
for (index=0; index < replicaManager.GetReplicaCount(); index++)
{
t = (TestObject *) replicaManager.GetReplicaAtIndex(index);
printf("%i. Remote object owned by %s:%i with test integer=%i\n", index+1, rakPeer->PlayerIDToDottedIP(t->owner), t->owner.port, t->testInteger );
}
printf("\n");
}
else if (ch=='c' || ch=='C')
{
printf("Enter port to connect to: ");
gets(userInput);
if (userInput[0]==0)
strcpy(userInput, "60000");
unsigned short port;
port=atoi(userInput);
printf("Enter IP address to connect to: ");
gets(userInput);
if (userInput[0]==0)
strcpy(userInput, "127.0.0.1");
rakPeer->Connect(userInput, port, 0, 0);
printf("Attemping connection...\n");
}
else if (ch=='d' || ch=='D')
{
for (index=0; index < MAX_PEERS; index++)
{
playerId=rakPeer->GetPlayerIDFromIndex(index);
if (playerId==UNASSIGNED_PLAYER_ID)
break;
printf("%i. %s:%i\n", index+1, rakPeer->PlayerIDToDottedIP(playerId), playerId.port);
}
if (index>0)
{
printf("Enter the number of the system to disconnect from: ");
gets(userInput);
playerId=rakPeer->GetPlayerIDFromIndex(atoi(userInput)-1);
if (playerId==UNASSIGNED_PLAYER_ID)
printf("Invalid number\n");
else
{
printf("Disconnecting from %s:%i\n", rakPeer->PlayerIDToDottedIP(playerId), playerId.port);
rakPeer->CloseConnection(playerId, true, 0);
}
}
}
else if (ch=='t' || ch=='T')
{
if (localObject==0)
{
if (NetworkIDGenerator::GetExternalPlayerID()==UNASSIGNED_PLAYER_ID)
{
printf("You cannot create objects before connecting at least once.\n");
printf("This sample requires you do so so you know your external IP\nThis is copied to TestObject::owner.\n");
}
else
{
localObject=new TestObject;
replicaManager.Construct(localObject, false, UNASSIGNED_PLAYER_ID, true); // Construct on all other systems
localObject->owner=NetworkIDGenerator::GetExternalPlayerID();
// Don't let other systems mess with our junk
replicaManager.DisableReplicaInterfaces(localObject, REPLICA_RECEIVE_DESTRUCTION | REPLICA_RECEIVE_SERIALIZE | REPLICA_RECEIVE_SCOPE_CHANGE);
printf("Local test object created.\n");
}
}
else
{
replicaManager.Destruct(localObject, UNASSIGNED_PLAYER_ID, true); // Send the destruct message to all
delete localObject;
localObject=0;
printf("Local test object destroyed.\n");
}
}
else if (ch=='i' || ch=='I')
{
if (localObject)
{
localObject->testInteger++;
replicaManager.SignalSerializeNeeded(localObject, UNASSIGNED_PLAYER_ID, true);
printf("Local object test integer is now %i\n", localObject->testInteger);
}
else
{
printf("Local object is not instantiated.\n");
}
}
}
#ifdef _WIN32
Sleep(30);
#else
usleep(30 * 1000);
#endif
}
rakPeer->Disconnect(100, 0);
delete localObject;
RakNetworkFactory::DestroyRakPeerInterface(rakPeer);
return 1;
}
syntax highlighted by Code2HTML, v. 0.9.1