This section describes how to use the basic features of PiaSync in applications.
Before initializing PiaSync, you must initialize communication modules such as PiaInet and PiaTransport. For more information about initializing modules other than PiaSync, see the programming manuals for each module and the API reference.
Perform the setup steps. If they succeed, initialize PiaSync.
PiaSync can assign different data IDs to each category of data sent or received by your application. You can use it to configure which data to send. For each data ID, configure the size of the data in the Setting structure. You pass in this structure when creating an instance of the PiaSync class. When starting synchronization (described below), you configure which of these data categories to send.
Set the same values in the structure on all synchronized stations.
nn::Result result;
// Initialize PiaSync. // Set up PiaCommon and PiaTransport in advance. result = nn::pia::sync::Initialize(); PIA_ASSERT(result.IsSuccess()); result = nn::pia::sync::BeginSetup(); PIA_ASSERT(result.IsSuccess()); // Get the Transport singleton. nn::pia::transport::Transport* pTransport = nn::pia::transport::Transport::GetInstance(); PIA_ASSERT(PIA_IS_VALID_POINTER(pTransport)); // SyncProtocol の作成 u32 m_hSyncProtocol = pTransport->CreateProtocol<sync::SyncProtocol>(); // Make sure that the return value is not 0. The CreateProtocol() function returns 0 if the same protocol ID already exists. PIA_ASSERT(m_hSyncProtocol != 0); // Prepare the settings to pass to Initialize. nn::pia::sync::SyncProtocol::Setting setting; // Sets the value for delay. // If frames are frequently skipped because synchronization data could not be received from all other stations, increasing this value may alleviate the problem. setting.m_MaxDelay = 10; setting.m_TimeoutFrame = 240; // You can configure up to SyncProtocol::DATA_ID_NUM of the m_DataUnitSize object. // Set the data size for each data ID. In this example, values are set for the 0th, 1st, and 2nd data IDs. // You can use the SyncProtocol::GetDataUnitSizeMax() function to find out the largest size this value can be set to. setting.m_DataUnitSize[0] = 16; setting.m_DataUnitSize[1] = 32; setting.m_DataUnitSize[2] = 32; // Set the compression level for synchronization data. // Set this to either COMPRESSION_LEVEL_NONE, COMPRESSION_LEVEL_LOW, COMPRESSION_LEVEL_MIDDLE, or COMPRESSION_LEVEL_HIGH. // There is no compression if you specify COMPRESSION_LEVEL_NONE. COMPRESSION_LEVEL_LOW emphasizes speed, whereas COMPRESSION_LEVEL_HIGH emphasizes compression. // COMPRESSION_LEVEL_MIDDLE sits between these two. // The amount of time it takes to do this can vary largely depending on the CPU performance of the platform. setting.m_DataCompressionLevel = nn::pia::sync::SyncProtocol::COMPRESSION_LEVEL_MIDDLE; // Initializing SyncProtocol. result = pTransport->GetProtocol<nn::pia::sync::SyncProtocol>(m_hSyncProtocol)->Initialize(setting); PIA_ASSERT(result.IsSuccess()); result = nn::pia::sync::EndSetup(); PIA_ASSERT(result.IsSuccess()); |
Synchronization starts when all stations in the session are ready. Multiple categories of data can be configured. When synchronization starts, you can specify which categories of data to send from the local station.
The data IDs that the local station sends are shared with all synchronized stations. It is okay for different stations to send different data IDs.
// Configure which data types the local station sends. (Set with a positive value in the Setting struct that is passed to the SyncProtocol::Initialize() function.)
// This example sets bits to specify the 0th and 2nd data IDs. bit32 usingDataIdBitmap = 0x5; // Specify the input delay. This value must be equal to or less than SyncProtocol::Setting::m_MaxDelay configured during initialization. // This value is not configured as is for the input delay. Note that the maximum value specified for each station is configured as the input delay. u32 delay = 4; // The sendPeriod parameter is used to specify the number of calls to SyncProtocol::Step before sending or resending. // The appropriate value depends on the size of the synchronization data being sent, connectivity issues such as packet loss rate, and the configured input delay. // Sending takes place more often with a smaller value, but if the synchronization data is large, it may be sent after a longer interval than the specified value. // Try several values to see which provides the best performance. // The send interval must be equal to or less than the input delay minus the one-way communication delay to achieve synchronized communication without skipping frames. u32 sendPeriod = 1; // Start synchronization. // Synchronization begins when all stations in the session have begun synchronized communication. result = nn::pia::transport::Transport::GetInstance()->GetProtocol<nn::pia::sync::SyncProtocol>(m_hSyncProtocol)->Start(usingDataIdBitmap, delay, sendPeriod); PIA_ASSERT(result.IsSuccess()); |
During synchronization, frames are synchronized through dispatches and through sending and receiving data.
If the connection degrades and user input from other stations is not synchronized with the local station by the configured input delay time, the nn::pia::sync::SyncProtocol::Step function fails and returns the error code nn::pia::sync::ResultSyncDataIsNotArrivedYet. In this situation, stop the game until user input is synchronized again.
If you proceed with the game assuming that there was no user input, the game state may become out of sync between stations.
nn::pia::sync::SyncProtocol* pSyncProtocol = nn::pia::transport::Transport::GetInstance()->GetProtocol<nn::pia::sync::SyncProtocol>(m_hSyncProtocol);
// You must call the Scheduler::Dispatch function of NEX for keep-alive communication with the server. if (nn::nex::Scheduler::GetInstance() != NULL) { nn::nex::Scheduler::GetInstance()->DispatchAll(); } // Call the nn::pia::common::Scheduler::Dispatch() function at regular intervals. nn::pia::common::Scheduler::GetInstance()->Dispatch(); // 送信データを設定します if (pSyncProtocol->NeedSetData()) { // Prepare the data to send, and specify it to the SyncProtocol object. for (u32 i = 0; i < 3; ++i) { // Check whether the local station is supposed to send this data, and if so, set it. // Provide a dataBuffer variable of the appropriate size for the data ID. if (pSyncProtocol->NeedSetData(i)) { result = pSyncProtocol->SetData(i, &dataBuffer); PIA_ASSERT(result.IsSuccess()); } } } // Advance one frame. result = pSyncProtocol->Step(); if (result.IsFailure()) { if (result == nn::pia::ResultDataIsNotArrivedYet()) { // The data has not yet arrived. Keep calling Dispatch until it does. while (true) { // This example just calls Sleep, but in the actual implementation of your game, do something like skipping one game frame and waiting for the next one. Sleep(33); if (Inet_IsConnected() == false) { // End because the communication has been disconnected. break; } if (nn::pia::session::Session::GetInstance()->CheckConnectionError().IsFailure()) { // End because the communication has been disconnected. break; } if (nn::nex::Scheduler::GetInstance() != NULL) { nn::nex::Scheduler::GetInstance()->DispatchAll(); } nn::pia::common::Scheduler::GetInstance()->Dispatch(); // Data does not need to be set because frames have not been advanced since setting it. PIA_ASSERT(pSyncProtocol->NeedSetData() == false); result = pSyncProtocol->Step(); if (result != nn::pia::ResultDataIsNotArrivedYet()) { break; } } } else if (result == nn::pia::ResultDataIsNotSet()) { // Data has already been set, so this result does not occur. PIA_ASSERT(false); } else { // Other error. } } // Get the received data. for (u32 i = 0; i < MAX_STATIONS; ++i) { nn::pia::StationId stationId = stationIdList[i]; // Check the local state and the state of the other station, and receive data if it is available. if (pSyncProtocol->CanGetData() && pSyncProtocol->CheckEntry(stationId)) { bit32 usingDataIdBitmap = pSyncProtocol->GetUsingDataIdBitmap(stationId); for (u32 j = 0; j < 3; ++j) { // Check whether the remote station is sending this data, and if so, get it. // Provide a dataBuffer variable of the appropriate size for the data ID. if ((0x1 << j) & usingDataIdBitmap) { result = pSyncProtocol->GetData(stationId, j, &dataBuffer); PIA_ASSERT(result.IsSuccess()); } } } } // Call the nn::pia::common::Scheduler::Dispatch() function again so that you can immediately process the data sent on the protocol. nn::pia::common::Scheduler::GetInstance()->Dispatch(); // Confirm the connection state. if (Inet_IsConnected() == false) { // End because the communication has been disconnected. // Error handling. } if (nn::pia::session::Session::GetInstance()->CheckConnectionError().IsFailure()) { // End because the communication has been disconnected. // Error handling. } |
You can check the synchronization state of the local station. There are five states: STATE_NOT_SYNCHRONIZED, STATE_WAITING, STATE_STARTING, STATE_SYNCHRONIZED, and STATE_ENDING.
sync::State | Description |
---|---|
STATE_NOT_SYNCHRONIZED | The station is not synchronized. |
STATE_WAITING |
The station is preparing for synchronization. The station enters this state after calling the SyncProtocol::Start() function while the state is STATE_NOT_SYNCHRONIZED. |
STATE_STARTING | The station is starting synchronization. This state is reached when all stations in the session are in STATE_WAITING. In this state, the local station listens for key input for the specified delay and then starts synchronization. |
STATE_SYNCHRONIZED | The station is synchronized. The station enters this state when the preparations in STATE_STARTING are complete. |
STATE_ENDING | The station is finishing synchronization. The station enters this state when it calls SyncProtocol::End() or when another station notifies it that communication has ended. You can get the reason for the termination from the SyncProtocol::GetLastEndReason() function. |
The following figure shows state transitions.
When the state is STATE_WAITING, STATE_STARTING, STATE_SYNCHRONIZED, or STATE_ENDING, you must call the sync::SyncProtocol::Step() function in sync with the game frames. When the state is STATE_STARTING or STATE_SYNCHRONIZED, you must call the sync::SyncProtocol::SetData() function before calling the sync::SyncProtocol::Step() function. You can only get data using the sync::SyncProtocol::GetData() function when the state is STATE_SYNCHRONIZED.
nn::pia::sync::State syncState = nn::pia::transport::Transport::GetInstance()->GetProtocol<nn::pia::sync::SyncProtocol>(m_hSyncProtocol)->GetState();
switch(syncState) { case nn::pia::sync::SyncProtocol::STATE_NOT_SYNCHRONIZED: // Not synchronized. break; case nn::pia::sync::SyncProtocol::STATE_WAITING: // Waiting to start synchronization. break; case nn::pia::sync::SyncProtocol::STATE_STARTING: // Starting synchronization. break; case nn::pia::sync::SyncProtocol::STATE_SYNCHRONIZED: // Synchronizing. break; case nn::pia::sync::SyncProtocol::STATE_ENDING: // Ending synchronization. break; } |
When you call the SyncProtocol::End() function, the state of the local station changes to SyncProtocol::STATE_ENDING, and the station notifies the other stations that it is ending communication. Synchronization then ends, and the state of the local station changes to SyncProtocol::STATE_NOT_SYNCHRONIZED.
The state could change to SyncProtocol::STATE_ENDING (ending synchronization) for one of the reasons indicated in the synchronization state transition table, even if the local station does not call the SyncProtocol::End() function. In this situation, the stations may end synchronization at different times (on different frame numbers).
// End synchronization.
nn::pia::transport::Transport::GetInstance()->GetProtocol<nn::pia::sync::SyncProtocol>(m_hSyncProtocol)->End(); |
Immediately after calling the SyncProtocol::End() function, the state is SyncProtocol::STATE_ENDING. If you want to call SyncProtocol::Start() again, you must call the SyncProtocol::Step() function for several frames, until the state becomes SyncProtocol::STATE_NOT_SYNCHRONIZED.
When you have finished using PiaSync, perform finalization. The order of finalization is the opposite of initialization: finalize the module before finalizing the communication module (such as PiaLocal or PiaInet) and the PiaTransport module.
// Finalize SyncProtocol.
nn::pia::transport::Transport::GetInstance()->GetProtocol<nn::pia::sync::SyncProtocol>(m_hSyncProtocol)->Finalize(); // Destroy SyncProtocol. nn::pia::transport::Transport::GetInstance()->DestroyProtocol(m_hSyncProtocol); // sync finalization processing. nn::pia::sync::Finalize(); |
After finalizing PiaSync, you perform post-processing in the reverse order from initialization, finalizing PiaTransport first, and then finalizing the communication modules.
If the size of the synchronization data is large, it may not fit into a single packet. In such cases, the data is split and sent in multiple packets. If you want to reduce the number of packets, you must make the synchronization data smaller.
The larger the round-trip time (RTT), the larger the size of the synchronization data to be sent, but transmitted synchronization data is stored in one packet even when it reaches the maximum size. The maximum value of the total data size of each data ID is shown in the following table (when transmitting all frame data). You can prevent wasted space in the packets when they are sent by making sure the size of the data corresponding to each data ID is a multiple of 4. The data for each data ID is split up so that it is 4-byte aligned anyway.
The actual values differ depending on factors such as whether it is Internet or local communication, MTU, and packet signing. Use these values for reference purposes when deciding synchronization data sizes.
(Internet communication / MTU=1240 / with signing / compression disabled)
Delay | Number of Data IDs | |||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | |
1 | 568 | 564 | 560 | 556 | 552 | 548 | 544 | 540 | 536 | 532 | 528 | 524 | 520 | 516 | 512 | 508 |
2 | 376 | 372 | 368 | 364 | 360 | 356 | 352 | 348 | 344 | 340 | 336 | 332 | 328 | 324 | 320 | 316 |
3 | 224 | 220 | 216 | 212 | 208 | 204 | 200 | 196 | 192 | 188 | 184 | 180 | 176 | 172 | 168 | 164 |
4 | 184 | 180 | 176 | 172 | 168 | 164 | 160 | 156 | 152 | 148 | 144 | 140 | 136 | 132 | 128 | 124 |
5 | 156 | 152 | 148 | 144 | 140 | 136 | 132 | 128 | 124 | 120 | 116 | 112 | 108 | 104 | 100 | 96 |
6 | 156 | 152 | 148 | 144 | 140 | 136 | 132 | 128 | 124 | 120 | 116 | 112 | 108 | 104 | 100 | 96 |
7 | 136 | 132 | 128 | 124 | 120 | 116 | 112 | 108 | 104 | 100 | 96 | 92 | 88 | 84 | 80 | 76 |
8 | 136 | 132 | 128 | 124 | 120 | 116 | 112 | 108 | 104 | 100 | 96 | 92 | 88 | 84 | 80 | 76 |
9 | 120 | 116 | 112 | 108 | 104 | 100 | 96 | 92 | 88 | 84 | 80 | 76 | 72 | 68 | 64 | 60 |
10 | 120 | 116 | 112 | 108 | 104 | 100 | 96 | 92 | 88 | 84 | 80 | 76 | 72 | 68 | 64 | 60 |
Synchronization data can be compressed. Compression can be disabled or, when compression is enabled, SyncProtocol::Setting::m_DataCompressionLevel can be used to set the compression level.
By enabling compression, more data can be stored in each packet, and the size of the packets can be reduced. However, when compression is enabled, compression and decompression increase the amount of processing required. Compression and decompression is performed by the common::Scheduler::Dispatch() function.
Data compression uses zlib. If you attempt to compress data with little redundancy or an extremely small size, the compressed data might be larger than the original data.