Snapshot.cpp (40758B)
1 /* 2 =========================================================================== 3 4 Doom 3 BFG Edition GPL Source Code 5 Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. 6 7 This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). 8 9 Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify 10 it under the terms of the GNU General Public License as published by 11 the Free Software Foundation, either version 3 of the License, or 12 (at your option) any later version. 13 14 Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, 15 but WITHOUT ANY WARRANTY; without even the implied warranty of 16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 GNU General Public License for more details. 18 19 You should have received a copy of the GNU General Public License 20 along with Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>. 21 22 In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. 23 24 If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. 25 26 =========================================================================== 27 */ 28 #pragma hdrstop 29 #include "../idlib/precompiled.h" 30 31 idCVar net_verboseSnapshot( "net_verboseSnapshot", "0", CVAR_INTEGER|CVAR_NOCHEAT, "Verbose snapshot code to help debug snapshot problems. Greater the number greater the spam" ); 32 idCVar net_verboseSnapshotCompression( "net_verboseSnapshotCompression", "0", CVAR_INTEGER|CVAR_NOCHEAT, "Verbose snapshot code to help debug snapshot problems. Greater the number greater the spam" ); 33 idCVar net_verboseSnapshotReport( "net_verboseSnapshotReport", "0", CVAR_INTEGER|CVAR_NOCHEAT, "Verbose snapshot code to help debug snapshot problems. Greater the number greater the spam" ); 34 35 idCVar net_ssTemplateDebug( "net_ssTemplateDebug", "0", CVAR_BOOL, "Debug snapshot template states" ); 36 idCVar net_ssTemplateDebug_len( "net_ssTemplateDebug_len", "32", CVAR_INTEGER, "Offset to start template state debugging" ); 37 idCVar net_ssTemplateDebug_start( "net_ssTemplateDebug_start", "0", CVAR_INTEGER, "length of template state to print in debugging" ); 38 39 /* 40 ======================== 41 InDebugRange 42 Helper function for net_ssTemplateDebug debugging 43 ======================== 44 */ 45 bool InDebugRange( int i ) { 46 return ( i >= net_ssTemplateDebug_start.GetInteger() && i < net_ssTemplateDebug_start.GetInteger() + net_ssTemplateDebug_len.GetInteger() ); 47 } 48 /* 49 ======================== 50 PrintAlign 51 Helper function for net_ssTemplateDebug debugging 52 ======================== 53 */ 54 void PrintAlign( const char * text ) { 55 idLib::Printf( "%25s: 0x", text ); 56 } 57 58 /* 59 ======================== 60 idSnapShot::objectState_t::Print 61 Helper function for net_ssTemplateDebug debugging 62 ======================== 63 */ 64 void idSnapShot::objectState_t::Print( const char * name ) { 65 66 unsigned int start = (unsigned int)net_ssTemplateDebug_start.GetInteger(); 67 unsigned int end = Min( (unsigned int)buffer.Size(), start + net_ssTemplateDebug_len.GetInteger() ); 68 69 PrintAlign( va( "%s: [sz %d]", name, buffer.Size() ) ); 70 71 for ( unsigned int i = start; i < end; i++ ) { 72 idLib::Printf( "%02X", buffer[i] ); 73 } 74 idLib::Printf("\n"); 75 } 76 77 /* 78 ======================== 79 idSnapShot::objectBuffer_t::Alloc 80 ======================== 81 */ 82 void idSnapShot::objectBuffer_t::Alloc( int s ) { 83 //assert( mem.IsMapHeap() ); 84 if ( !verify( s < SIZE_NOT_STALE ) ) { 85 idLib::FatalError( "s >= SIZE_NOT_STALE" ); 86 } 87 _Release(); 88 data = (byte *)Mem_Alloc( s + 1, TAG_NETWORKING ); 89 size = s; 90 data[size] = 1; 91 } 92 93 /* 94 ======================== 95 idSnapShot::objectBuffer_t::AddRef 96 ======================== 97 */ 98 void idSnapShot::objectBuffer_t::_AddRef() { 99 if ( data != NULL ) { 100 assert( size > 0 ); 101 assert( data[size] < 255 ); 102 data[size]++; 103 } 104 } 105 106 /* 107 ======================== 108 idSnapShot::objectBuffer_t::Release 109 ======================== 110 */ 111 void idSnapShot::objectBuffer_t::_Release() { 112 //assert( mem.IsMapHeap() ); 113 if ( data != NULL ) { 114 assert( size > 0 ); 115 if ( --data[size] == 0 ) { 116 Mem_Free( data ); 117 } 118 data = NULL; 119 size = 0; 120 } 121 } 122 123 /* 124 ======================== 125 idSnapShot::objectBuffer_t::operator= 126 ======================== 127 */ 128 void idSnapShot::objectBuffer_t::operator=( const idSnapShot::objectBuffer_t & other ) { 129 //assert( mem.IsMapHeap() ); 130 if ( this != &other ) { 131 _Release(); 132 data = other.data; 133 size = other.size; 134 _AddRef(); 135 } 136 } 137 138 /* 139 ======================== 140 idSnapShot::idSnapShot 141 ======================== 142 */ 143 idSnapShot::idSnapShot() : 144 time( 0 ), 145 recvTime( 0 ) 146 { 147 } 148 149 /* 150 ======================== 151 idSnapShot::idSnapShot 152 ======================== 153 */ 154 idSnapShot::idSnapShot( const idSnapShot & other ) : time( 0 ), recvTime(0) { 155 *this = other; 156 } 157 158 /* 159 ======================== 160 idSnapShot::~idSnapShot 161 ======================== 162 */ 163 idSnapShot::~idSnapShot() { 164 Clear(); 165 } 166 167 /* 168 ======================== 169 idSnapShot::Clear 170 ======================== 171 */ 172 void idSnapShot::Clear() { 173 time = 0; 174 recvTime = 0; 175 for ( int i = 0; i < objectStates.Num(); i++ ) { 176 FreeObjectState( i ); 177 } 178 objectStates.Clear(); 179 allocatedObjs.Shutdown(); 180 } 181 182 /* 183 ======================== 184 idSnapShot::operator= 185 ======================== 186 */ 187 void idSnapShot::operator=( const idSnapShot & other ) { 188 //assert( mem.IsMapHeap() ); 189 190 if ( this != &other ) { 191 for ( int i = other.objectStates.Num(); i < objectStates.Num(); i++ ) { 192 FreeObjectState( i ); 193 } 194 objectStates.AssureSize( other.objectStates.Num(), NULL ); 195 for ( int i = 0; i < objectStates.Num(); i++ ) { 196 const objectState_t & otherState = *other.objectStates[i]; 197 if ( objectStates[i] == NULL ) { 198 objectStates[i] = allocatedObjs.Alloc(); 199 } 200 objectState_t & state = *objectStates[i]; 201 state.objectNum = otherState.objectNum; 202 state.buffer = otherState.buffer; 203 state.visMask = otherState.visMask; 204 state.stale = otherState.stale; 205 state.deleted = otherState.deleted; 206 state.changedCount = otherState.changedCount; 207 state.expectedSequence = otherState.expectedSequence; 208 state.createdFromTemplate = otherState.createdFromTemplate; 209 } 210 time = other.time; 211 recvTime = other.recvTime; 212 } 213 } 214 215 /* 216 ======================== 217 idSnapShot::PeekDeltaSequence 218 ======================== 219 */ 220 void idSnapShot::PeekDeltaSequence( const char * deltaMem, int deltaSize, int & sequence, int & baseSequence ) { 221 lzwCompressionData_t lzwData; 222 idLZWCompressor lzwCompressor( &lzwData ); 223 224 lzwCompressor.Start( (uint8*)deltaMem, deltaSize ); 225 lzwCompressor.ReadAgnostic( sequence ); 226 lzwCompressor.ReadAgnostic( baseSequence ); 227 } 228 229 /* 230 ======================== 231 idSnapShot::ReadDeltaForJob 232 ======================== 233 */ 234 bool idSnapShot::ReadDeltaForJob( const char * deltaMem, int deltaSize, int visIndex, idSnapShot * templateStates ) { 235 236 bool report = net_verboseSnapshotReport.GetBool(); 237 net_verboseSnapshotReport.SetBool( false ); 238 239 lzwCompressionData_t lzwData; 240 idZeroRunLengthCompressor rleCompressor; 241 idLZWCompressor lzwCompressor( &lzwData ); 242 int bytesRead = 0; // how many uncompressed bytes we read in. Used to figure out compression ratio 243 244 lzwCompressor.Start( (uint8*)deltaMem, deltaSize ); 245 246 // Skip past sequence and baseSequence 247 int sequence = 0; 248 int baseSequence = 0; 249 250 lzwCompressor.ReadAgnostic( sequence ); 251 lzwCompressor.ReadAgnostic( baseSequence ); 252 lzwCompressor.ReadAgnostic( time ); 253 bytesRead += sizeof( int ) * 3; 254 255 int objectNum = 0; 256 uint16 delta = 0; 257 258 259 while ( lzwCompressor.ReadAgnostic( delta, true ) == sizeof( delta ) ) { 260 bytesRead += sizeof( delta ); 261 262 objectNum += delta; 263 if ( objectNum >= 0xFFFF ) { 264 // full delta 265 if ( net_verboseSnapshotCompression.GetBool() ) { 266 float compRatio = static_cast<float>( deltaSize ) / static_cast<float>( bytesRead ); 267 idLib::Printf( "Snapshot (%d/%d). ReadSize: %d DeltaSize: %d Ratio: %.3f\n", sequence, baseSequence, bytesRead, deltaSize, compRatio ); 268 } 269 return true; 270 } 271 272 objectState_t & state = FindOrCreateObjectByID( objectNum ); 273 274 objectSize_t newsize = 0; 275 lzwCompressor.ReadAgnostic( newsize ); 276 bytesRead += sizeof( newsize ); 277 278 if ( newsize == SIZE_STALE ) { 279 NET_VERBOSESNAPSHOT_PRINT( "read delta: object %d goes stale\n", objectNum ); 280 // sanity 281 bool oldVisible = ( state.visMask & ( 1 << visIndex ) ) != 0; 282 if ( !oldVisible ) { 283 NET_VERBOSESNAPSHOT_PRINT( "ERROR: unexpected already stale\n" ); 284 } 285 state.visMask &= ~( 1 << visIndex ); 286 state.stale = true; 287 // We need to make sure we haven't freed stale objects. 288 assert( state.buffer.Size() > 0 ); 289 // no more data 290 continue; 291 } else if ( newsize == SIZE_NOT_STALE ) { 292 NET_VERBOSESNAPSHOT_PRINT( "read delta: object %d no longer stale\n", objectNum ); 293 // sanity 294 bool oldVisible = ( state.visMask & ( 1 << visIndex ) ) != 0; 295 if ( oldVisible ) { 296 NET_VERBOSESNAPSHOT_PRINT( "ERROR: unexpected not stale\n" ); 297 } 298 state.visMask |= ( 1 << visIndex ); 299 state.stale = false; 300 // the latest state is packed in, get the new size and continue reading the new state 301 lzwCompressor.ReadAgnostic( newsize ); 302 bytesRead += sizeof( newsize ); 303 } 304 305 objectState_t * objTemplateState = templateStates->FindObjectByID( objectNum ); 306 307 if ( newsize == 0 ) { 308 // object deleted: reset state now so next one to use it doesn't have old data 309 state.deleted = false; 310 state.stale = false; 311 state.changedCount = 0; 312 state.expectedSequence = 0; 313 state.visMask = 0; 314 state.buffer._Release(); 315 state.createdFromTemplate = false; 316 317 if ( objTemplateState != NULL && objTemplateState->buffer.Size() && objTemplateState->expectedSequence < baseSequence ) { 318 idLib::PrintfIf( net_ssTemplateDebug.GetBool(), "Clearing old template state[%d] [%d<%d]\n", objectNum, objTemplateState->expectedSequence, baseSequence ); 319 objTemplateState->deleted = false; 320 objTemplateState->stale = false; 321 objTemplateState->changedCount = 0; 322 objTemplateState->expectedSequence = 0; 323 objTemplateState->visMask = 0; 324 objTemplateState->buffer._Release(); 325 } 326 327 } else { 328 329 // new state? 330 bool debug = false; 331 if ( state.buffer.Size() == 0 ) { 332 state.createdFromTemplate = true; 333 // Brand new state 334 if ( objTemplateState != NULL && objTemplateState->buffer.Size() > 0 && sequence >= objTemplateState->expectedSequence ) { 335 idLib::PrintfIf( net_ssTemplateDebug.GetBool(), "\nAdding basestate for new object %d (for SS %d/%d. obj base created in ss %d) deltaSize: %d\n", objectNum, sequence, baseSequence, objTemplateState->expectedSequence, deltaSize ); 336 state.buffer = objTemplateState->buffer; 337 338 if ( net_ssTemplateDebug.GetBool() ) { 339 state.Print( "SPAWN STATE" ); 340 debug = true; 341 PrintAlign( "DELTA STATE" ); 342 } 343 } else if ( net_ssTemplateDebug.GetBool() ) { 344 idLib::Printf("\nNew snapobject[%d] in snapshot %d/%d but no basestate found locally so creating new\n", objectNum, sequence, baseSequence ); 345 } 346 } else { 347 state.createdFromTemplate = false; 348 } 349 350 // the buffer shrank or stayed the same 351 objectBuffer_t newbuffer( newsize ); 352 rleCompressor.Start( NULL, &lzwCompressor, newsize ); 353 objectSize_t compareSize = Min( state.buffer.Size(), newsize ); 354 for ( objectSize_t i = 0; i < compareSize; i++ ) { 355 byte b = rleCompressor.ReadByte(); 356 newbuffer[i] = state.buffer[i] + b; 357 358 if ( debug && InDebugRange( i ) ) { 359 idLib::Printf( "%02X", b ); 360 } 361 } 362 // Catch leftover 363 if ( newsize > compareSize ) { 364 rleCompressor.ReadBytes( newbuffer.Ptr() + compareSize, newsize - compareSize ); 365 366 if ( debug ) { 367 for ( objectSize_t i = compareSize; i < newsize; i++ ) { 368 if ( InDebugRange( i ) ) { 369 idLib::Printf( "%02X", newbuffer[i] ); 370 } 371 } 372 } 373 374 } 375 state.buffer = newbuffer; 376 state.changedCount = sequence; 377 bytesRead += sizeof( byte ) * newsize; 378 if ( debug ) { 379 idLib::Printf( "\n" ); 380 state.Print( "NEW STATE" ); 381 } 382 383 if ( report ) { 384 idLib::Printf( " Obj %d Compressed: Size %d \n", objectNum, rleCompressor.CompressedSize() ); 385 } 386 } 387 #ifdef SNAPSHOT_CHECKSUMS 388 extern uint32 SnapObjChecksum( const uint8 * data, int length ); 389 if ( state.buffer.Size() > 0 ) { 390 uint32 checksum = 0; 391 lzwCompressor.ReadAgnostic( checksum ); 392 bytesRead += sizeof( checksum ); 393 if ( !verify( checksum == SnapObjChecksum( state.buffer.Ptr(), state.buffer.Size() ) ) ) { 394 idLib::Error(" Invalid snapshot checksum" ); 395 } 396 } 397 #endif 398 } 399 // partial delta 400 return false; 401 } 402 403 /* 404 ======================== 405 idSnapShot::SubmitObjectJob 406 ======================== 407 */ 408 409 void idSnapShot::SubmitObjectJob( const submitDeltaJobsInfo_t & submitDeltaJobsInfo, 410 objectState_t * newState, 411 objectState_t * oldState, 412 objParms_t *& baseObjParm, 413 objParms_t *& curObjParm, 414 objHeader_t *& curHeader, 415 uint8 *& curObjDest, 416 lzwParm_t *& curlzwParm 417 ) { 418 assert( newState != NULL || oldState != NULL ); 419 assert_16_byte_aligned( curHeader ); 420 assert_16_byte_aligned( curObjDest ); 421 422 int32 dataSize = newState != NULL ? newState->buffer.Size() : 0; 423 int totalSize = OBJ_DEST_SIZE_ALIGN16( dataSize ); 424 425 if ( curObjParm - submitDeltaJobsInfo.objParms >= submitDeltaJobsInfo.maxObjParms ) { 426 idLib::Error( "Out of parms for snapshot jobs.\n"); 427 } 428 429 // Check to see if we are out of dest write space, and need to flush the jobs 430 bool needToSubmit = ( curObjDest - submitDeltaJobsInfo.objMemory ) + totalSize >= submitDeltaJobsInfo.maxObjMemory; 431 needToSubmit |= ( curHeader - submitDeltaJobsInfo.headers >= submitDeltaJobsInfo.maxHeaders ); 432 433 if ( needToSubmit ) { 434 // If this obj will put us over the limit, then submit the jobs now, and start over re-using the same buffers 435 SubmitLZWJob( submitDeltaJobsInfo, baseObjParm, curObjParm, curlzwParm, true ); 436 curHeader = submitDeltaJobsInfo.headers; 437 curObjDest = submitDeltaJobsInfo.objMemory; 438 } 439 440 // Setup obj parms 441 assert( submitDeltaJobsInfo.visIndex < 256 ); 442 curObjParm->visIndex = submitDeltaJobsInfo.visIndex; 443 curObjParm->destHeader = curHeader; 444 curObjParm->dest = curObjDest; 445 446 memset( &curObjParm->newState, 0, sizeof( curObjParm->newState ) ); 447 memset( &curObjParm->oldState, 0, sizeof( curObjParm->oldState ) ); 448 449 if ( newState != NULL ) { 450 assert( newState->buffer.Size() <= 65535 ); 451 452 curObjParm->newState.valid = 1; 453 curObjParm->newState.data = newState->buffer.Ptr(); 454 curObjParm->newState.size = newState->buffer.Size(); 455 curObjParm->newState.objectNum = newState->objectNum; 456 curObjParm->newState.visMask = newState->visMask; 457 } 458 459 if ( oldState != NULL ) { 460 assert( oldState->buffer.Size() <= 65535 ); 461 462 curObjParm->oldState.valid = 1; 463 curObjParm->oldState.data = oldState->buffer.Ptr(); 464 curObjParm->oldState.size = oldState->buffer.Size(); 465 curObjParm->oldState.objectNum = oldState->objectNum; 466 curObjParm->oldState.visMask = oldState->visMask; 467 } 468 469 assert_16_byte_aligned( curObjParm ); 470 assert_16_byte_aligned( curObjParm->newState.data ); 471 assert_16_byte_aligned( curObjParm->oldState.data ); 472 473 SnapshotObjectJob( curObjParm ); 474 475 // Advance past header + data 476 curObjDest += totalSize; 477 478 // Advance parm pointer 479 curObjParm++; 480 481 // Advance header pointer 482 curHeader++; 483 } 484 485 /* 486 ======================== 487 idSnapShot::SubmitLZWJob 488 Take the current list of delta'd + zlre processed objects, and write them to the lzw stream. 489 ======================== 490 */ 491 void idSnapShot::SubmitLZWJob( 492 const submitDeltaJobsInfo_t & writeDeltaInfo, 493 objParms_t *& baseObjParm, // Pointer to the first obj parm for the current stream 494 objParms_t *& curObjParm, // Current obj parm 495 lzwParm_t *& curlzwParm, // Current delta parm 496 bool saveDictionary 497 ) { 498 int numObjects = curObjParm - baseObjParm; 499 500 if ( numObjects == 0 ) { 501 return; // Nothing to do 502 } 503 504 if ( curlzwParm - writeDeltaInfo.lzwParms >= writeDeltaInfo.maxDeltaParms ) { 505 idLib::Error( "SubmitLZWJob: Not enough lzwParams.\n" ); 506 return; // Can't do anymore 507 } 508 509 curlzwParm->numObjects = numObjects; 510 curlzwParm->headers = writeDeltaInfo.headers; // We always start grabbing from the beggining of the memory (it's reused, with fences to protect memory sharing) 511 curlzwParm->curTime = this->GetTime(); 512 curlzwParm->baseTime = writeDeltaInfo.oldSnap->GetTime(); 513 curlzwParm->baseSequence = writeDeltaInfo.baseSequence; 514 curlzwParm->fragmented = ( curlzwParm != writeDeltaInfo.lzwParms ); 515 curlzwParm->saveDictionary = saveDictionary; 516 517 curlzwParm->ioData = writeDeltaInfo.lzwInOutData; 518 519 LZWJob( curlzwParm ); 520 521 curlzwParm++; 522 523 // Set base so it now points to where the parms start for the new stream 524 baseObjParm = curObjParm; 525 } 526 527 /* 528 ======================== 529 idSnapShot::GetTemplateState 530 Helper function for getting template objectState. 531 newState parameter is optional and is just used for debugging/printf comparison of the template and actual state 532 ======================== 533 */ 534 idSnapShot::objectState_t * idSnapShot::GetTemplateState( int objNum, idSnapShot * templateStates, idSnapShot::objectState_t * newState /*=NULL*/ ) { 535 objectState_t * oldState = NULL; 536 int spawnedStateIndex = templateStates->FindObjectIndexByID( objNum ); 537 if ( spawnedStateIndex >= 0 ) { 538 oldState = templateStates->objectStates[ spawnedStateIndex ]; 539 540 if ( net_ssTemplateDebug.GetBool() ) { 541 idLib::Printf( "\nGetTemplateState[%d]\n", objNum ); 542 oldState->Print( "SPAWN STATE" ); 543 if ( newState != NULL ) { 544 newState->Print( "CUR STATE" ); 545 } 546 } 547 } 548 return oldState; 549 } 550 551 /* 552 ======================== 553 idSnapShot::SubmitWriteDeltaToJobs 554 ======================== 555 */ 556 void idSnapShot::SubmitWriteDeltaToJobs( const submitDeltaJobsInfo_t & submitDeltaJobInfo ) { 557 objParms_t * curObjParms = submitDeltaJobInfo.objParms; 558 objParms_t * baseObjParms = submitDeltaJobInfo.objParms; 559 lzwParm_t * curlzwParms = submitDeltaJobInfo.lzwParms; 560 objHeader_t * curHeader = submitDeltaJobInfo.headers; 561 uint8 * curObjMemory = submitDeltaJobInfo.objMemory; 562 563 submitDeltaJobInfo.lzwInOutData->numlzwDeltas = 0; 564 submitDeltaJobInfo.lzwInOutData->lzwBytes = 0; 565 submitDeltaJobInfo.lzwInOutData->fullSnap = false; 566 567 int j = 0; 568 569 int numOldStates = submitDeltaJobInfo.oldSnap->objectStates.Num(); 570 571 for ( int i = 0; i < objectStates.Num(); i++ ) { 572 objectState_t & newState = *objectStates[i]; 573 if ( !verify( newState.buffer.Size() > 0 ) ) { 574 // you CANNOT have a valid ss obj state w/ size = 0: this will be interpreted as a delete in ::ReadDelta and this will completely throw 575 // off delta compression, eventually resulting in a checksum error that is a pain to track down. 576 idLib::Warning( "Snap obj [%d] state.size <= 0... skipping ", newState.objectNum ); 577 continue; 578 } 579 580 if ( j >= numOldStates ) { 581 // We no longer have old objects to compare to. 582 // All objects are new from this point on. 583 584 objectState_t * oldState = GetTemplateState( newState.objectNum, submitDeltaJobInfo.templateStates, &newState ); 585 SubmitObjectJob( submitDeltaJobInfo, &newState, oldState, baseObjParms, curObjParms, curHeader, curObjMemory, curlzwParms ); 586 continue; 587 } 588 589 // write any deleted entities up to this one 590 for ( ; j < numOldStates && newState.objectNum > submitDeltaJobInfo.oldSnap->objectStates[j]->objectNum; j++ ) { 591 objectState_t & oldState = *submitDeltaJobInfo.oldSnap->objectStates[j]; 592 593 if ( ( oldState.stale && !oldState.deleted ) || oldState.buffer.Size() <= 0 ) { 594 continue; // Don't delete objects that are stale and not marked as deleted 595 } 596 597 SubmitObjectJob( submitDeltaJobInfo, NULL, &oldState, baseObjParms, curObjParms, curHeader, curObjMemory, curlzwParms ); 598 } 599 600 if ( j >= numOldStates ) { 601 continue; // Went past end of old list deleting objects 602 } 603 604 // Beyond this point, we may have old state to compare against 605 objectState_t & submittedOldState = *submitDeltaJobInfo.oldSnap->objectStates[j]; 606 objectState_t * oldState = &submittedOldState; 607 608 if ( newState.objectNum == oldState->objectNum ) { 609 if ( oldState->buffer.Size() == 0 ) { 610 // New state (even though snapObj existed, its size was zero) 611 oldState = GetTemplateState( newState.objectNum, submitDeltaJobInfo.templateStates, &newState ); 612 } 613 614 SubmitObjectJob( submitDeltaJobInfo, &newState, oldState, baseObjParms, curObjParms, curHeader, curObjMemory, curlzwParms ); 615 j++; 616 } else { 617 // Different object, this one is new, 618 // Spawned 619 oldState = GetTemplateState( newState.objectNum, submitDeltaJobInfo.templateStates, &newState ); 620 SubmitObjectJob( submitDeltaJobInfo, &newState, oldState, baseObjParms, curObjParms, curHeader, curObjMemory, curlzwParms ); 621 } 622 } 623 // Finally, remove any entities at the end 624 for ( ; j < submitDeltaJobInfo.oldSnap->objectStates.Num(); j++ ) { 625 objectState_t & oldState = *submitDeltaJobInfo.oldSnap->objectStates[j]; 626 627 if ( ( oldState.stale && !oldState.deleted ) || oldState.buffer.Size() <= 0 ) { 628 continue; // Don't delete objects that are stale and not marked as deleted 629 } 630 631 SubmitObjectJob( submitDeltaJobInfo, NULL, &oldState, baseObjParms, curObjParms, curHeader, curObjMemory, curlzwParms ); 632 } 633 634 // Submit any objects that are left over (will be all if they all fit up to this point) 635 SubmitLZWJob( submitDeltaJobInfo, baseObjParms, curObjParms, curlzwParms, false ); 636 } 637 638 /* 639 ======================== 640 idSnapShot::ReadDelta 641 ======================== 642 */ 643 bool idSnapShot::ReadDelta( idFile * file, int visIndex ) { 644 645 file->ReadBig( time ); 646 647 int objectNum = 0; 648 uint16 delta = 0; 649 while ( file->ReadBig( delta ) == sizeof( delta ) ) { 650 objectNum += delta; 651 if ( objectNum >= 0xFFFF ) { 652 // full delta 653 return true; 654 } 655 objectState_t & state = FindOrCreateObjectByID( objectNum ); 656 objectSize_t newsize = 0; 657 file->ReadBig( newsize ); 658 659 if ( newsize == SIZE_STALE ) { 660 NET_VERBOSESNAPSHOT_PRINT( "read delta: object %d goes stale\n", objectNum ); 661 // sanity 662 bool oldVisible = ( state.visMask & ( 1 << visIndex ) ) != 0; 663 if ( !oldVisible ) { 664 NET_VERBOSESNAPSHOT_PRINT( "ERROR: unexpected already stale\n" ); 665 } 666 state.visMask &= ~( 1 << visIndex ); 667 state.stale = true; 668 // We need to make sure we haven't freed stale objects. 669 assert( state.buffer.Size() > 0 ); 670 // no more data 671 continue; 672 } else if ( newsize == SIZE_NOT_STALE ) { 673 NET_VERBOSESNAPSHOT_PRINT( "read delta: object %d no longer stale\n", objectNum ); 674 // sanity 675 bool oldVisible = ( state.visMask & ( 1 << visIndex ) ) != 0; 676 if ( oldVisible ) { 677 NET_VERBOSESNAPSHOT_PRINT( "ERROR: unexpected not stale\n" ); 678 } 679 state.visMask |= ( 1 << visIndex ); 680 state.stale = false; 681 // the latest state is packed in, get the new size and continue reading the new state 682 file->ReadBig( newsize ); 683 } 684 685 if ( newsize == 0 ) { 686 // object deleted 687 state.buffer._Release(); 688 } else { 689 objectBuffer_t newbuffer( newsize ); 690 objectSize_t compareSize = Min( newsize, state.buffer.Size() ); 691 692 for ( objectSize_t i = 0; i < compareSize; i++ ) { 693 uint8 delta = 0; 694 file->ReadBig<byte>( delta ); 695 newbuffer[i] = state.buffer[i] + delta; 696 } 697 698 if ( newsize > compareSize ) { 699 file->Read( newbuffer.Ptr() + compareSize, newsize - compareSize ); 700 } 701 702 state.buffer = newbuffer; 703 state.changedCount++; 704 } 705 706 #ifdef SNAPSHOT_CHECKSUMS 707 if ( state.buffer.Size() > 0 ) { 708 unsigned int checksum = 0; 709 file->ReadBig( checksum ); 710 assert( checksum == MD5_BlockChecksum( state.buffer.Ptr(), state.buffer.Size() ) ); 711 } 712 #endif 713 } 714 715 // partial delta 716 return false; 717 } 718 719 /* 720 ======================== 721 idSnapShot::WriteObject 722 ======================== 723 */ 724 void idSnapShot::WriteObject( idFile * file, int visIndex, objectState_t * newState, objectState_t * oldState, int & lastobjectNum ) { 725 assert( newState != NULL || oldState != NULL ); 726 727 bool visChange = false; // visibility changes will be signified with a 0xffff state size 728 bool visSendState = false; // the state is sent when an entity is no longer stale 729 730 // Compute visibility changes 731 // (we need to do this before writing out object id, because we may not need to write out the id if we early out) 732 // (when we don't write out the id, we assume this is an "ack" when we deserialize the objects) 733 if ( newState != NULL && oldState != NULL ) { 734 // Check visibility 735 assert( newState->objectNum == oldState->objectNum ); 736 737 if ( visIndex > 0 ) { 738 bool oldVisible = ( oldState->visMask & ( 1 << visIndex ) ) != 0; 739 bool newVisible = ( newState->visMask & ( 1 << visIndex ) ) != 0; 740 741 // Force visible if we need to either create or destroy this object 742 newVisible |= ( newState->buffer.Size() == 0 ) != ( oldState->buffer.Size() == 0 ); 743 744 if ( !oldVisible && !newVisible ) { 745 // object is stale and ack'ed for this client, write nothing (see 'same object' below) 746 return; 747 } else if ( oldVisible && !newVisible ) { 748 NET_VERBOSESNAPSHOT_PRINT( "object %d to client %d goes stale\n", newState->objectNum, visIndex ); 749 visChange = true; 750 visSendState = false; 751 } else if ( !oldVisible && newVisible ) { 752 NET_VERBOSESNAPSHOT_PRINT( "object %d to client %d no longer stale\n", newState->objectNum, visIndex ); 753 visChange = true; 754 visSendState = true; 755 } 756 } 757 758 // Same object, write a delta (never early out during vis changes) 759 if ( !visChange && newState->buffer.Size() == oldState->buffer.Size() && 760 ( ( newState->buffer.Ptr() == oldState->buffer.Ptr() ) || memcmp( newState->buffer.Ptr(), oldState->buffer.Ptr(), newState->buffer.Size() ) == 0 ) ) { 761 // same state, write nothing 762 return; 763 } 764 } 765 766 // Get the id of the object we are writing out 767 uint16 objectNum; 768 if ( newState != NULL ) { 769 objectNum = newState->objectNum; 770 } else if ( oldState != NULL ) { 771 objectNum = oldState->objectNum; 772 } else { 773 objectNum = 0; 774 } 775 776 assert( objectNum == 0 || objectNum > lastobjectNum ); 777 778 // Write out object id (using delta) 779 uint16 objectDelta = objectNum - lastobjectNum; 780 file->WriteBig( objectDelta ); 781 lastobjectNum = objectNum; 782 783 if ( newState == NULL ) { 784 // Deleted, write 0 size 785 assert( oldState != NULL ); 786 file->WriteBig<objectSize_t>( 0 ); 787 } else if ( oldState == NULL ) { 788 // New object, write out full state 789 assert( newState != NULL ); 790 // delta against an empty snap 791 file->WriteBig( newState->buffer.Size() ); 792 file->Write( newState->buffer.Ptr(), newState->buffer.Size() ); 793 } else { 794 // Compare to last object 795 assert( newState != NULL && oldState != NULL ); 796 assert( newState->objectNum == oldState->objectNum ); 797 798 if ( visChange ) { 799 // fake size indicates vis state change 800 // NOTE: we may still send a real size and a state below, for 'no longer stale' transitions 801 // TMP: send 0xFFFF for going stale and 0xFFFF - 1 for no longer stale 802 file->WriteBig<objectSize_t>( visSendState ? SIZE_NOT_STALE : SIZE_STALE ); 803 } 804 if ( !visChange || visSendState ) { 805 806 objectSize_t compareSize = Min( newState->buffer.Size(), oldState->buffer.Size() ); // Get the number of bytes that overlap 807 808 file->WriteBig( newState->buffer.Size() ); // Write new size 809 810 // Compare bytes that overlap 811 for ( objectSize_t b = 0; b < compareSize; b++ ) { 812 file->WriteBig<byte>( ( 0xFF + 1 + newState->buffer[b] - oldState->buffer[b] ) & 0xFF ); 813 } 814 815 // Write leftover 816 if ( newState->buffer.Size() > compareSize ) { 817 file->Write( newState->buffer.Ptr() + oldState->buffer.Size(), newState->buffer.Size() - compareSize ); 818 } 819 } 820 } 821 822 #ifdef SNAPSHOT_CHECKSUMS 823 if ( ( !visChange || visSendState ) && newState != NULL ) { 824 assert( newState->buffer.Size() > 0 ); 825 unsigned int checksum = MD5_BlockChecksum( newState->buffer.Ptr(), newState->buffer.Size() ); 826 file->WriteBig( checksum ); 827 } 828 #endif 829 } 830 831 /* 832 ======================== 833 idSnapShot::PrintReport 834 ======================== 835 */ 836 void idSnapShot::PrintReport() { 837 } 838 839 /* 840 ======================== 841 idSnapShot::WriteDelta 842 ======================== 843 */ 844 bool idSnapShot::WriteDelta( idSnapShot & old, int visIndex, idFile * file, int maxLength, int optimalLength ) { 845 file->WriteBig( time ); 846 847 int objectHeaderSize = sizeof( uint16 ) + sizeof( objectSize_t ); 848 #ifdef SNAPSHOT_CHECKSUMS 849 objectHeaderSize += sizeof( unsigned int ); 850 #endif 851 852 int lastobjectNum = 0; 853 int j = 0; 854 855 for ( int i = 0; i < objectStates.Num(); i++ ) { 856 objectState_t & newState = *objectStates[i]; 857 858 if ( optimalLength > 0 && file->Length() >= optimalLength ) { 859 return false; 860 } 861 862 if ( !verify( newState.buffer.Size() < maxLength ) ) { 863 // If the new state's size is > the max packet size, we'll never be able to send it! 864 idLib::Warning( "Snap obj [%d] state.size > max packet length. Skipping... ", newState.objectNum ); 865 continue; 866 } else if ( !verify( newState.buffer.Size() > 0 ) ) { 867 // you CANNOT have a valid ss obj state w/ size = 0: this will be interpreted as a delete in ::ReadDelta and this will completely throw 868 // off delta compression, eventually resulting in a checksum error that is a pain to track down. 869 idLib::Warning( "Snap obj [%d] state.size <= 0... skipping ", newState.objectNum ); 870 continue; 871 } 872 873 if ( file->Length() + objectHeaderSize + newState.buffer.Size() >= maxLength ) { 874 return false; 875 } 876 877 if ( j >= old.objectStates.Num() ) { 878 // delta against an empty snap 879 WriteObject( file, visIndex, &newState, NULL, lastobjectNum ); 880 continue; 881 } 882 883 // write any deleted entities up to this one 884 for ( ; newState.objectNum > old.objectStates[j]->objectNum; j++ ) { 885 if ( file->Length() + objectHeaderSize >= maxLength ) { 886 return false; 887 } 888 objectState_t & oldState = *old.objectStates[j]; 889 WriteObject( file, visIndex, NULL, &oldState, lastobjectNum ); 890 } 891 892 // Beyond this point, we have old state to compare against 893 objectState_t & oldState = *old.objectStates[j]; 894 895 if ( newState.objectNum == oldState.objectNum ) { 896 // FIXME: We don't need to early out if WriteObject determines that we won't send the object due to being stale 897 if ( file->Length() + objectHeaderSize + newState.buffer.Size() >= maxLength ) { 898 return false; 899 } 900 WriteObject( file, visIndex, &newState, &oldState, lastobjectNum ); 901 j++; 902 } else { 903 if ( file->Length() + objectHeaderSize + newState.buffer.Size() >= maxLength ) { 904 return false; 905 } 906 907 // Different object, this one is new, write the full state 908 WriteObject( file, visIndex, &newState, NULL, lastobjectNum ); 909 } 910 } 911 // Finally, remove any entities at the end 912 for ( ; j < old.objectStates.Num(); j++ ) { 913 if ( file->Length() + objectHeaderSize >= maxLength ) { 914 return false; 915 } 916 917 if ( optimalLength > 0 && file->Length() >= optimalLength ) { 918 return false; 919 } 920 921 objectState_t & oldState = *old.objectStates[j]; 922 WriteObject( file, visIndex, NULL, &oldState, lastobjectNum ); 923 } 924 if ( file->Length() + 2 >= maxLength ) { 925 return false; 926 } 927 uint16 objectDelta = 0xFFFF - lastobjectNum; 928 file->WriteBig( objectDelta ); 929 930 return true; 931 } 932 933 /* 934 ======================== 935 idSnapShot::AddObject 936 ======================== 937 */ 938 idSnapShot::objectState_t * idSnapShot::S_AddObject( int objectNum, uint32 visMask, const char * data, int _size, const char * tag ) { 939 objectSize_t size = _size; 940 objectState_t & state = FindOrCreateObjectByID( objectNum ); 941 state.visMask = visMask; 942 if ( state.buffer.Size() == size && state.buffer.NumRefs() == 1 ) { 943 // re-use the same buffer 944 memcpy( state.buffer.Ptr(), data, size ); 945 } else { 946 objectBuffer_t buffer( size ); 947 memcpy( buffer.Ptr(), data, size ); 948 state.buffer = buffer; 949 } 950 return &state; 951 } 952 953 /* 954 ======================== 955 idSnapShot::CopyObject 956 ======================== 957 */ 958 959 bool idSnapShot::CopyObject( const idSnapShot & oldss, int objectNum, bool forceStale ) { 960 961 int oldIndex = oldss.FindObjectIndexByID( objectNum ); 962 if ( oldIndex == -1 ) { 963 return false; 964 } 965 966 const objectState_t & oldState = *oldss.objectStates[oldIndex]; 967 objectState_t & newState = FindOrCreateObjectByID( objectNum ); 968 969 newState.buffer = oldState.buffer; 970 newState.visMask = oldState.visMask; 971 newState.stale = oldState.stale; 972 newState.deleted = oldState.deleted; 973 newState.changedCount = oldState.changedCount; 974 newState.expectedSequence = oldState.expectedSequence; 975 newState.createdFromTemplate = oldState.createdFromTemplate; 976 977 if ( forceStale ) { 978 newState.visMask = 0; 979 } 980 981 return true; 982 } 983 984 /* 985 ======================== 986 idSnapShot::CompareObject 987 start, end, and oldStart can optionally be passed in to compare subsections of the object 988 default parameters will compare entire object 989 ======================== 990 */ 991 int idSnapShot::CompareObject( const idSnapShot * oldss, int objectNum, int start, int end, int oldStart ) { 992 if ( oldss == NULL ) { 993 return 0; 994 } 995 996 assert( FindObjectIndexByID( objectNum ) >= 0 ); 997 998 objectState_t & newState = FindOrCreateObjectByID( objectNum ); 999 1000 int oldIndex = oldss->FindObjectIndexByID( objectNum ); 1001 if ( oldIndex == -1 ) { 1002 return ( end == 0 ? newState.buffer.Size() : end - start ); // Didn't exist in the old state, so we take the hit on the entire size 1003 } 1004 1005 objectState_t & oldState = const_cast< objectState_t & >( *oldss->objectStates[oldIndex] ); 1006 1007 int bytes = 0; 1008 1009 int oldOffset = oldStart - start; 1010 int commonSize = ( newState.buffer.Size() <= oldState.buffer.Size() - oldOffset ) ? newState.buffer.Size() : oldState.buffer.Size() - oldOffset; 1011 if ( end == 0 ) { 1012 // default 0 means compare the whole thing 1013 end = commonSize; 1014 1015 // Get leftover (if any) 1016 bytes = ( newState.buffer.Size() > oldState.buffer.Size() ) ? ( newState.buffer.Size() - oldState.buffer.Size() ) : 0; 1017 } else { 1018 1019 // else only compare up to end or the max buffer and dont include leftover 1020 end = Min( commonSize, end ); 1021 } 1022 1023 for ( int b = start; b < end; b++ ) { 1024 if ( verify( b >= 0 && b < (int)newState.buffer.Size() && b + oldOffset >= 0 && b + oldOffset < (int)oldState.buffer.Size() ) ) { 1025 bytes += ( newState.buffer[b] != oldState.buffer[b + oldOffset] ) ? 1 : 0; 1026 } 1027 } 1028 1029 return bytes; 1030 } 1031 1032 /* 1033 ======================== 1034 idSnapShot::GetObjectMsgByIndex 1035 ======================== 1036 */ 1037 int idSnapShot::GetObjectMsgByIndex( int i, idBitMsg & msg, bool ignoreIfStale ) const { 1038 if ( i < 0 || i >= objectStates.Num() ) { 1039 return -1; 1040 } 1041 objectState_t & state = *objectStates[i]; 1042 if ( state.stale && ignoreIfStale ) { 1043 return -1; 1044 } 1045 msg.InitRead( state.buffer.Ptr(), state.buffer.Size() ); 1046 return state.objectNum; 1047 } 1048 1049 /* 1050 ======================== 1051 idSnapShot::ObjectIsStaleByIndex 1052 ======================== 1053 */ 1054 bool idSnapShot::ObjectIsStaleByIndex( int i ) const { 1055 if ( i < 0 || i >= objectStates.Num() ) { 1056 return false; 1057 } 1058 return objectStates[i]->stale; 1059 } 1060 1061 /* 1062 ======================== 1063 idSnapShot::ObjectChangedCountByIndex 1064 ======================== 1065 */ 1066 int idSnapShot::ObjectChangedCountByIndex( int i ) const { 1067 if ( i < 0 || i >= objectStates.Num() ) { 1068 return false; 1069 } 1070 return objectStates[i]->changedCount; 1071 } 1072 1073 /* 1074 ======================== 1075 idSnapShot::FindObjectIndexByID 1076 ======================== 1077 */ 1078 int idSnapShot::FindObjectIndexByID( int objectNum ) const { 1079 int i = BinarySearch( objectNum ); 1080 if ( i >= 0 && i < objectStates.Num() && objectStates[i]->objectNum == objectNum ) { 1081 return i; 1082 } 1083 return -1; 1084 } 1085 1086 /* 1087 ======================== 1088 idSnapShot::BinarySearch 1089 ======================== 1090 */ 1091 int idSnapShot::BinarySearch( int objectNum ) const { 1092 int lo = 0; 1093 int hi = objectStates.Num(); 1094 while ( hi != lo ) { 1095 int mid = ( hi + lo ) >> 1; 1096 1097 if ( objectStates[mid]->objectNum == objectNum ) { 1098 return mid; // Early out if we can 1099 } 1100 1101 if ( objectStates[mid]->objectNum < objectNum ) { 1102 lo = mid + 1; 1103 } else { 1104 hi = mid; 1105 } 1106 } 1107 return hi; 1108 } 1109 1110 /* 1111 ======================== 1112 idSnapShot::FindOrCreateObjectByID 1113 ======================== 1114 */ 1115 idSnapShot::objectState_t & idSnapShot::FindOrCreateObjectByID( int objectNum ) { 1116 //assert( mem.IsMapHeap() ); 1117 1118 int i = BinarySearch( objectNum ); 1119 1120 if ( i >= 0 && i < objectStates.Num() && objectStates[i]->objectNum == objectNum ) { 1121 return *objectStates[i]; 1122 } 1123 1124 objectState_t * newstate = allocatedObjs.Alloc(); 1125 newstate->objectNum = objectNum; 1126 1127 objectStates.Insert( newstate, i ); 1128 1129 return *objectStates[i]; 1130 } 1131 1132 /* 1133 ======================== 1134 idSnapShot::FindObjectByID 1135 ======================== 1136 */ 1137 idSnapShot::objectState_t * idSnapShot::FindObjectByID( int objectNum ) const { 1138 1139 //assert( mem.IsMapHeap() ); 1140 1141 int i = BinarySearch( objectNum ); 1142 1143 if ( i >= 0 && i < objectStates.Num() && objectStates[i]->objectNum == objectNum ) { 1144 return objectStates[i]; 1145 } 1146 1147 return NULL; 1148 } 1149 1150 /* 1151 ======================== 1152 idSnapShot::CleanupEmptyStates 1153 ======================== 1154 */ 1155 void idSnapShot::CleanupEmptyStates() { 1156 for ( int i = objectStates.Num() - 1; i >= 0 ; i-- ) { 1157 if ( objectStates[i]->buffer.Size() == 0 ) { 1158 FreeObjectState( i ); 1159 objectStates.RemoveIndex(i); 1160 } 1161 } 1162 } 1163 1164 /* 1165 ======================== 1166 idSnapShot::UpdateExpectedSeq 1167 ======================== 1168 */ 1169 void idSnapShot::UpdateExpectedSeq( int newSeq ) { 1170 for ( int i = 0; i < objectStates.Num(); i++ ) { 1171 if ( objectStates[i]->expectedSequence == -2 ) { 1172 objectStates[i]->expectedSequence = newSeq; 1173 } 1174 } 1175 } 1176 1177 /* 1178 ======================== 1179 idSnapShot::FreeObjectState 1180 ======================== 1181 */ 1182 void idSnapShot::FreeObjectState( int index ) { 1183 assert( objectStates[index] != NULL ); 1184 //assert( mem.IsMapHeap() ); 1185 objectStates[index]->buffer._Release(); 1186 allocatedObjs.Free( objectStates[index] ); 1187 objectStates[index] = NULL; 1188 } 1189 1190 /* 1191 ======================== 1192 idSnapShot::ApplyToExistingState 1193 Take uncompressed state in msg and add it to existing state 1194 ======================== 1195 */ 1196 void idSnapShot::ApplyToExistingState( int objId, idBitMsg & msg ) { 1197 objectState_t * objectState = FindObjectByID( objId ); 1198 if ( !verify( objectState != NULL ) ) { 1199 return; 1200 } 1201 1202 if ( !objectState->createdFromTemplate ) { 1203 // We were created this from a template, so we shouldn't be applying it again 1204 if ( net_ssTemplateDebug.GetBool() ) { 1205 idLib::Printf( "NOT ApplyToExistingState[%d] because object was created from existing base state. %d\n", objId, objectState->expectedSequence ); 1206 objectState->Print( "SS STATE" ); 1207 } 1208 return; 1209 } 1210 1211 // Debug print the template (spawn) and delta state 1212 if ( net_ssTemplateDebug.GetBool() ) { 1213 idLib::Printf( "\nApplyToExistingState[%d]. buffer size: %d msg size: %d\n", objId, objectState->buffer.Size(), msg.GetSize() ); 1214 objectState->Print( "DELTA STATE" ); 1215 1216 PrintAlign( "SPAWN STATE" ); 1217 for ( int i = 0; i < msg.GetSize(); i++ ) { 1218 if ( InDebugRange( i ) ) { 1219 idLib::Printf( "%02X", msg.GetReadData()[i] ); 1220 } 1221 } 1222 idLib::Printf( "\n" ); 1223 } 1224 1225 // Actually apply it 1226 for ( objectSize_t i = 0; i < Min( objectState->buffer.Size(), msg.GetSize() ); i++ ) { 1227 objectState->buffer[i] += msg.GetReadData()[i]; 1228 } 1229 1230 // Debug print the final state 1231 if ( net_ssTemplateDebug.GetBool() ) { 1232 objectState->Print( "NEW STATE" ); 1233 idLib::Printf( "\n" ); 1234 } 1235 } 1236 1237 #if 0 1238 CONSOLE_COMMAND( serializeQTest, "Serialization Sanity Test", 0 ) { 1239 1240 byte buffer[1024]; 1241 memset( buffer, 0, sizeof( buffer ) ); 1242 1243 float values[] = { 0.0001f, 0.001f, 0.01f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f, 0.9f, 0.999f, 1244 1.0f, 1.01f, 1.1f, 10.0f, 10.1f, 10.101f, 100.0f, 101.0f, 101.1f, 101.101f }; 1245 int num = sizeof(values) / sizeof(float); 1246 1247 idLib::Printf("\n^3Testing SerializeQ and SerializeUQ \n"); 1248 1249 { 1250 idBitMsg writeBitMsg; 1251 writeBitMsg.InitWrite( buffer, sizeof(buffer) ); 1252 idSerializer writeSerializer( writeBitMsg, true ); 1253 1254 for( int i = 0; i < num; i++ ) { 1255 writeSerializer.SerializeUQ( values[i], 255.0f, 16 ); 1256 writeSerializer.SerializeQ( values[i], 128.0f, 16 ); 1257 } 1258 } 1259 1260 { 1261 idBitMsg readBitMsg; 1262 readBitMsg.InitRead( buffer, sizeof( buffer ) ); 1263 idSerializer readSerializer( readBitMsg, false ); 1264 1265 for( int i = 0; i < num; i++ ) { 1266 1267 float resultUQ = -999.0f; 1268 float resultQ = -999.0f; 1269 1270 readSerializer.SerializeUQ( resultUQ, 255.0f, 16 ); 1271 readSerializer.SerializeQ( resultQ, 128.0f, 16 ); 1272 1273 float errorUQ = idMath::Fabs( (resultUQ - values[i]) ) / values[i]; 1274 float errorQ = idMath::Fabs( (resultQ - values[i]) ) / values[i]; 1275 1276 idLib::Printf( "%s%f SerializeUQ: %f. Error: %f \n", errorUQ > 0.1f ? "^1": "", values[i], resultUQ, errorUQ ); 1277 idLib::Printf( "%s%f SerializeQ: %f. Error: %f \n", errorQ > 0.1f ? "^1": "", values[i], resultQ, errorQ ); 1278 } 1279 } 1280 } 1281 #endif