Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: orxonox.OLD/branches/network/src/lib/network/network_stream.cc @ 8174

Last change on this file since 8174 was 8174, checked in by rennerc, 18 years ago

reconnect to unused clientslots works now

File size: 21.3 KB
Line 
1/*
2   orxonox - the future of 3D-vertical-scrollers
3
4   Copyright (C) 2004 orx
5
6   This program is free software; you can redistribute it and/or modify
7   it under the terms of the GNU General Public License as published by
8   the Free Software Foundation; either version 2, or (at your option)
9   any later version.
10
11### File Specific:
12   main-programmer: claudio
13   co-programmer:
14*/
15
16
17/* this is for debug output. It just says, that all calls to PRINT() belong to the DEBUG_MODULE_NETWORK module
18   For more information refere to https://www.orxonox.net/cgi-bin/trac.cgi/wiki/DebugOutput
19*/
20#define DEBUG_MODULE_NETWORK
21
22
23#include "base_object.h"
24#include "network_protocol.h"
25#include "udp_socket.h"
26#include "udp_server_socket.h"
27#include "connection_monitor.h"
28#include "synchronizeable.h"
29#include "network_game_manager.h"
30#include "shared_network_data.h"
31#include "message_manager.h"
32#include "preferences.h"
33#include "zip.h"
34
35#include "src/lib/util/loading/resource_manager.h"
36
37#include "network_log.h"
38
39
40#include "lib/util/loading/factory.h"
41
42#include "debug.h"
43#include "class_list.h"
44#include <algorithm>
45
46/* include your own header */
47#include "network_stream.h"
48
49/* probably unnecessary */
50using namespace std;
51
52
53#define PACKAGE_SIZE  256
54
55
56NetworkStream::NetworkStream()
57    : DataStream()
58{
59  this->init();
60  /* initialize the references */
61  this->type = NET_CLIENT;
62}
63
64
65NetworkStream::NetworkStream( std::string host, int port )
66{
67  this->type = NET_CLIENT;
68  this->init();
69  this->peers[0].socket = new UdpSocket( host, port );
70  this->peers[0].userId = 0;
71  this->peers[0].isServer = true;
72  this->peers[0].connectionMonitor = new ConnectionMonitor( 0 );
73}
74
75
76NetworkStream::NetworkStream( int port )
77{
78  this->type = NET_SERVER;
79  this->init();
80  this->serverSocket = new UdpServerSocket(port);
81  this->bActive = true;
82}
83
84
85void NetworkStream::init()
86{
87  /* set the class id for the base object */
88  this->setClassID(CL_NETWORK_STREAM, "NetworkStream");
89  this->bActive = false;
90  this->serverSocket = NULL;
91  this->networkGameManager = NULL;
92  myHostId = 0;
93  currentState = 0;
94 
95  remainingBytesToWriteToDict = Preferences::getInstance()->getInt( "compression", "writedict", 0 );
96 
97  assert( Zip::getInstance()->loadDictionary( "testdict" ) );
98}
99
100
101NetworkStream::~NetworkStream()
102{
103  if ( this->serverSocket )
104  {
105    serverSocket->close();
106    delete serverSocket;
107  }
108
109  for ( PeerList::iterator i = peers.begin(); i!=peers.end(); i++)
110  {
111    if ( i->second.socket )
112    {
113      i->second.socket->disconnectServer();
114      delete i->second.socket;
115      i->second.socket = NULL;
116    }
117   
118    if ( i->second.handshake )
119    {
120      delete i->second.handshake;
121      i->second.handshake = NULL;
122    }
123  }
124 
125  if ( serverSocket )
126  {
127    delete serverSocket;
128    serverSocket = NULL;
129  }
130
131}
132
133
134void NetworkStream::createNetworkGameManager()
135{
136  this->networkGameManager = NetworkGameManager::getInstance();
137  // setUniqueID( maxCon+2 ) because we need one id for every handshake
138  // and one for handshake to reject client maxCon+1
139  this->networkGameManager->setUniqueID( SharedNetworkData::getInstance()->getNewUniqueID() );
140  MessageManager::getInstance()->setUniqueID( SharedNetworkData::getInstance()->getNewUniqueID() );
141}
142
143
144void NetworkStream::startHandshake()
145{
146  Handshake* hs = new Handshake(false);
147  hs->setUniqueID( 0 );
148  assert( peers[0].handshake == NULL );
149  peers[0].handshake = hs;
150//   peers[0].handshake->setSynchronized( true );
151  //this->connectSynchronizeable(*hs);
152  //this->connectSynchronizeable(*hs);
153  PRINTF(0)("NetworkStream: Handshake created: %s\n", hs->getName());
154}
155
156
157void NetworkStream::connectSynchronizeable(Synchronizeable& sync)
158{
159  this->synchronizeables.push_back(&sync);
160  sync.setNetworkStream( this );
161
162  this->bActive = true;
163}
164
165
166void NetworkStream::disconnectSynchronizeable(Synchronizeable& sync)
167{
168  // removing the Synchronizeable from the List.
169  std::list<Synchronizeable*>::iterator disconnectSynchro = std::find(this->synchronizeables.begin(), this->synchronizeables.end(), &sync);
170  if (disconnectSynchro != this->synchronizeables.end())
171    this->synchronizeables.erase(disconnectSynchro);
172 
173  oldSynchronizeables[sync.getUniqueID()] = SDL_GetTicks();
174}
175
176
177void NetworkStream::processData()
178{
179  int tick = SDL_GetTicks();
180 
181  currentState++;
182 
183  if ( this->type == NET_SERVER )
184  {
185    if ( serverSocket )
186      serverSocket->update();
187   
188    this->updateConnectionList();
189  }
190  else
191  {
192    if ( peers[0].socket && ( !peers[0].socket->isOk() || peers[0].connectionMonitor->hasTimedOut() ) )
193    {
194      PRINTF(1)("lost connection to server\n");
195
196      peers[0].socket->disconnectServer();
197      delete peers[0].socket;
198      peers[0].socket = NULL;
199
200      if ( peers[0].handshake )
201        delete peers[0].handshake;
202      peers[0].handshake = NULL;
203    }
204  }
205
206  cleanUpOldSyncList();
207  handleHandshakes();
208 
209  // order of up/downstream is important!!!!
210  // don't change it
211  handleDownstream( tick );
212  handleUpstream( tick );
213
214}
215
216void NetworkStream::updateConnectionList( )
217{
218  //check for new connections
219
220  NetworkSocket* tempNetworkSocket = serverSocket->getNewSocket();
221
222  if ( tempNetworkSocket )
223  {
224    int clientId;
225    if ( freeSocketSlots.size() >0 )
226    {
227      clientId = freeSocketSlots.back();
228      freeSocketSlots.pop_back();
229      peers[clientId].socket = tempNetworkSocket;
230      peers[clientId].handshake = new Handshake(true, clientId, this->networkGameManager->getUniqueID(), MessageManager::getInstance()->getUniqueID() );
231      peers[clientId].connectionMonitor = new ConnectionMonitor( clientId );
232      peers[clientId].handshake->setUniqueID(clientId);
233      peers[clientId].userId = clientId;
234      peers[clientId].isServer = false;
235    } else
236    {
237      clientId = 1;
238     
239      for ( PeerList::iterator it = peers.begin(); it != peers.end(); it++ )
240        if ( it->first >= clientId )
241          clientId = it->first + 1;
242     
243      peers[clientId].socket = tempNetworkSocket;
244      peers[clientId].handshake = new Handshake(true, clientId, this->networkGameManager->getUniqueID(), MessageManager::getInstance()->getUniqueID());
245      peers[clientId].handshake->setUniqueID(clientId);
246      peers[clientId].connectionMonitor = new ConnectionMonitor( clientId );
247      peers[clientId].userId = clientId;
248      peers[clientId].isServer = false;
249     
250      PRINTF(0)("num sync: %d\n", synchronizeables.size());
251    }
252
253    if ( clientId > MAX_CONNECTIONS )
254    {
255      peers[clientId].handshake->doReject( "too many connections" );
256      PRINTF(0)("Will reject client %d because there are to many connections!\n", clientId);
257    }
258    else
259
260    PRINTF(0)("New Client: %d\n", clientId);
261
262    //this->connectSynchronizeable(*handshakes[clientId]);
263  }
264
265  //check if connections are ok else remove them
266  for ( PeerList::iterator it = peers.begin(); it != peers.end(); )
267  {
268    if ( 
269          it->second.socket &&
270          ( 
271            !it->second.socket->isOk()  ||
272            it->second.connectionMonitor->hasTimedOut()
273          )
274       )
275    {
276      std::string reason = "disconnected";
277      if ( it->second.connectionMonitor->hasTimedOut() )
278        reason = "timeout";
279      PRINTF(0)("Client is gone: %d (%s)\n", it->second.userId, reason.c_str());
280     
281      //assert(false);
282
283      it->second.socket->disconnectServer();
284      delete it->second.socket;
285      it->second.socket = NULL;
286
287      if ( it->second.handshake )
288        delete it->second.handshake;
289      it->second.handshake = NULL;
290     
291      for ( SynchronizeableList::iterator it2 = synchronizeables.begin(); it2 != synchronizeables.end(); it2++ )
292      {
293        (*it2)->cleanUpUser( it->second.userId );
294      }
295
296      NetworkGameManager::getInstance()->signalLeftPlayer(it->second.userId);
297
298      freeSocketSlots.push_back( it->second.userId );
299     
300      PeerList::iterator delit = it;
301      it++;
302     
303      peers.erase( delit );
304     
305      continue;
306    }
307   
308    it++;
309  }
310
311
312}
313
314void NetworkStream::debug()
315{
316  if( this->isServer())
317    PRINT(0)(" Host ist Server with ID: %i\n", this->myHostId);
318  else
319    PRINT(0)(" Host ist Client with ID: %i\n", this->myHostId);
320
321  PRINT(0)(" Got %i connected Synchronizeables, showing active Syncs:\n", this->synchronizeables.size());
322  for (SynchronizeableList::iterator it = synchronizeables.begin(); it!=synchronizeables.end(); it++)
323  {
324    if( (*it)->beSynchronized() == true)
325      PRINT(0)("  Synchronizeable of class: %s::%s, with unique ID: %i, Synchronize: %i\n", (*it)->getClassName(), (*it)->getName(),
326               (*it)->getUniqueID(), (*it)->beSynchronized());
327  }
328  PRINT(0)(" Maximal Connections: %i\n", MAX_CONNECTIONS );
329
330}
331
332
333int NetworkStream::getSyncCount()
334{
335  int n = 0;
336  for (SynchronizeableList::iterator it = synchronizeables.begin(); it!=synchronizeables.end(); it++)
337    if( (*it)->beSynchronized() == true)
338      ++n;
339
340  //return synchronizeables.size();
341  return n;
342}
343
344/**
345 * check if handshakes completed
346 */
347void NetworkStream::handleHandshakes( )
348{
349  for ( PeerList::iterator it = peers.begin(); it != peers.end(); it++ )
350  {
351    if ( it->second.handshake )
352    {
353      if ( it->second.handshake->completed() )
354      {
355        if ( it->second.handshake->ok() )
356        {
357          if ( !it->second.handshake->allowDel() )
358          {
359            if ( type != NET_SERVER )
360            {
361              SharedNetworkData::getInstance()->setHostID( it->second.handshake->getHostId() );
362              myHostId = SharedNetworkData::getInstance()->getHostID();
363
364              this->networkGameManager = NetworkGameManager::getInstance();
365              this->networkGameManager->setUniqueID( it->second.handshake->getNetworkGameManagerId() );
366              MessageManager::getInstance()->setUniqueID( it->second.handshake->getMessageManagerId() );
367            }
368             
369
370            PRINT(0)("handshake finished id=%d\n", it->second.handshake->getNetworkGameManagerId());
371
372            it->second.handshake->del();
373          }
374          else
375          {
376            if ( it->second.handshake->canDel() )
377            {
378              if ( type == NET_SERVER )
379              {
380                handleNewClient( it->second.userId );
381              }
382             
383              PRINT(0)("handshake finished delete it\n");
384              delete it->second.handshake;
385              it->second.handshake = NULL;
386            }
387          }
388
389        }
390        else
391        {
392          PRINT(1)("handshake failed!\n");
393          it->second.socket->disconnectServer();
394        }
395      }
396    }
397  }
398}
399
400/**
401 * handle upstream network traffic
402 */
403void NetworkStream::handleUpstream( int tick )
404{
405  int offset;
406  int n;
407 
408  for ( PeerList::reverse_iterator peer = peers.rbegin(); peer != peers.rend(); peer++ )
409  {
410    offset = INTSIZE; //make already space for length
411   
412    if ( !peer->second.socket )
413      continue;
414   
415    n = Converter::intToByteArray( currentState, buf + offset, UDP_PACKET_SIZE - offset );
416    assert( n == INTSIZE );
417    offset += n;
418   
419    n = Converter::intToByteArray( peer->second.lastAckedState, buf + offset, UDP_PACKET_SIZE - offset );
420    assert( n == INTSIZE );
421    offset += n;
422   
423    n = Converter::intToByteArray( peer->second.lastRecvedState, buf + offset, UDP_PACKET_SIZE - offset );
424    assert( n == INTSIZE );
425    offset += n;
426   
427    for ( SynchronizeableList::iterator it = synchronizeables.begin(); it != synchronizeables.end(); it++ )
428    {
429      int oldOffset = offset;
430      Synchronizeable & sync = **it;
431     
432      if ( !sync.beSynchronized() || sync.getUniqueID() < 0 )
433        continue;
434
435      //if handshake not finished only sync handshake
436      if ( peer->second.handshake && sync.getLeafClassID() != CL_HANDSHAKE )
437        continue;
438     
439      if ( isServer() && sync.getLeafClassID() == CL_HANDSHAKE && sync.getUniqueID() != peer->second.userId )
440        continue;
441     
442      //do not sync null parent
443      if ( sync.getLeafClassID() == CL_NULL_PARENT )
444        continue;
445
446      assert( offset + INTSIZE <= UDP_PACKET_SIZE );
447     
448      //server fakes uniqueid=0 for handshake
449      if ( this->isServer() && sync.getUniqueID() < MAX_CONNECTIONS - 1 )
450        n = Converter::intToByteArray( 0, buf + offset, UDP_PACKET_SIZE - offset );
451      else
452        n = Converter::intToByteArray( sync.getUniqueID(), buf + offset, UDP_PACKET_SIZE - offset );
453      assert( n == INTSIZE );
454      offset += n;
455     
456      //make space for size
457      offset += INTSIZE;
458
459      n = sync.getStateDiff( peer->second.userId, buf + offset, UDP_PACKET_SIZE-offset, currentState, peer->second.lastAckedState, -1000 );
460      offset += n;
461      //NETPRINTF(0)("GGGGGEEEEETTTTT: %s (%d) %d\n",sync.getClassName(), sync.getUniqueID(), n);
462     
463      assert( Converter::intToByteArray( n, buf + offset - n - INTSIZE, INTSIZE ) == INTSIZE );
464     
465      //check if all bytes == 0 -> remove data
466      //TODO not all synchronizeables like this maybe add Synchronizeable::canRemoveZeroDiff()
467      bool allZero = true; 
468      for ( int i = 0; i < n; i++ ) 
469      { 
470         if ( buf[i+oldOffset+2*INTSIZE] != 0 ) 
471           allZero = false; 
472      } 
473
474      if ( allZero ) 
475      { 
476        //NETPRINTF(n)("REMOVE ZERO DIFF: %s (%d)\n", sync.getClassName(), sync.getUniqueID());
477        offset = oldOffset; 
478      } 
479
480     
481    }
482   
483    for ( SynchronizeableList::iterator it = synchronizeables.begin(); it != synchronizeables.end(); it++ )
484    {
485      Synchronizeable & sync = **it;
486     
487      if ( !sync.beSynchronized() || sync.getUniqueID() < 0 )
488        continue;
489     
490      sync.handleSentState( peer->second.userId, currentState, peer->second.lastAckedState );
491    }
492   
493    assert( Converter::intToByteArray( offset, buf, INTSIZE ) == INTSIZE );
494   
495    int compLength = Zip::getInstance()->zip( buf, offset, compBuf, UDP_PACKET_SIZE );
496   
497    if ( compLength < 0 )
498    {
499      PRINTF(1)("compression failed!\n");
500      continue;
501    }
502   
503    assert( peer->second.socket->writePacket( compBuf, compLength ) );
504   
505    if ( this->remainingBytesToWriteToDict > 0 )
506      writeToNewDict( buf, offset );
507   
508    peer->second.connectionMonitor->processUnzippedOutgoingPacket( tick, buf, offset, currentState );
509    peer->second.connectionMonitor->processZippedOutgoingPacket( tick, compBuf, compLength, currentState );
510   
511    //NETPRINTF(n)("send packet: %d userId = %d\n", offset, peer->second.userId);
512  }
513}
514
515/**
516 * handle downstream network traffic
517 */
518void NetworkStream::handleDownstream( int tick )
519{
520  int offset = 0;
521 
522  int length = 0;
523  int packetLength = 0;
524  int compLength = 0;
525  int uniqueId = 0;
526  int state = 0;
527  int ackedState = 0;
528  int fromState = 0;
529  int syncDataLength = 0;
530 
531  for ( PeerList::iterator peer = peers.begin(); peer != peers.end(); peer++ )
532  {
533   
534    if ( !peer->second.socket )
535      continue;
536
537    while ( 0 < (compLength = peer->second.socket->readPacket( compBuf, UDP_PACKET_SIZE )) )
538    {
539      peer->second.connectionMonitor->processZippedIncomingPacket( tick, compBuf, compLength );
540     
541      //PRINTF(0)("GGGGGOOOOOOOOOOTTTTTTTT: %d\n", compLength);
542      packetLength = Zip::getInstance()->unZip( compBuf, compLength, buf, UDP_PACKET_SIZE );
543     
544      if ( packetLength < 4*INTSIZE )
545      {
546        if ( packetLength != 0 )
547          PRINTF(1)("got too small packet: %d\n", packetLength);
548        continue;
549      }
550     
551      if ( this->remainingBytesToWriteToDict > 0 )
552        writeToNewDict( buf, packetLength );
553   
554      assert( Converter::byteArrayToInt( buf, &length ) == INTSIZE );
555      assert( Converter::byteArrayToInt( buf + INTSIZE, &state ) == INTSIZE );
556      assert( Converter::byteArrayToInt( buf + 2*INTSIZE, &fromState ) == INTSIZE );
557      assert( Converter::byteArrayToInt( buf + 3*INTSIZE, &ackedState ) == INTSIZE );
558      //NETPRINTF(n)("ackedstate: %d\n", ackedState);
559      offset = 4*INTSIZE;
560     
561      peer->second.connectionMonitor->processUnzippedIncomingPacket( tick, buf, offset, state, ackedState );
562
563      //NETPRINTF(n)("got packet: %d, %d\n", length, packetLength);
564   
565    //if this is an old state drop it
566      if ( state <= peer->second.lastRecvedState )
567        continue;
568   
569      if ( packetLength != length )
570      {
571        PRINTF(1)("real packet length (%d) and transmitted packet length (%d) do not match!\n", packetLength, length);
572        peer->second.socket->disconnectServer();
573        continue;
574      }
575     
576      while ( offset + 2*INTSIZE < length )
577      {
578        assert( offset > 0 );
579        assert( Converter::byteArrayToInt( buf + offset, &uniqueId ) == INTSIZE );
580        offset += INTSIZE;
581     
582        assert( Converter::byteArrayToInt( buf + offset, &syncDataLength ) == INTSIZE );
583        offset += INTSIZE;
584       
585        assert( syncDataLength > 0 );
586        assert( syncDataLength < 10000 );
587     
588        Synchronizeable * sync = NULL;
589       
590        for ( SynchronizeableList::iterator it = synchronizeables.begin(); it != synchronizeables.end(); it++ )
591        { 
592        //                                        client thinks his handshake has id 0!!!!!
593          if ( (*it)->getUniqueID() == uniqueId || ( uniqueId == 0 && (*it)->getUniqueID() == peer->second.userId ) )
594          {
595            sync = *it;
596            break;
597          }
598        }
599       
600        if ( sync == NULL )
601        {
602          PRINTF(0)("could not find sync with id %d. try to create it\n", uniqueId);
603          if ( oldSynchronizeables.find( uniqueId ) != oldSynchronizeables.end() )
604          {
605            offset += syncDataLength;
606            continue;
607          }
608         
609          if ( !peers[peer->second.userId].isServer )
610          {
611            offset += syncDataLength;
612            continue;
613          }
614         
615          int leafClassId;
616          if ( INTSIZE > length - offset )
617          {
618            offset += syncDataLength;
619            continue;
620          }
621
622          Converter::byteArrayToInt( buf + offset, &leafClassId );
623         
624          assert( leafClassId != 0 );
625       
626          BaseObject * b = NULL;
627          /* These are some small exeptions in creation: Not all objects can/should be created via Factory */
628          /* Exception 1: NullParent */
629          if( leafClassId == CL_NULL_PARENT || leafClassId == CL_SYNCHRONIZEABLE || leafClassId == CL_NETWORK_GAME_MANAGER )
630          {
631            PRINTF(1)("Can not create Class with ID %x!\n", (int)leafClassId);
632            offset += syncDataLength;
633            continue;
634          }
635          else
636            b = Factory::fabricate( (ClassID)leafClassId );
637
638          if ( !b )
639          {
640            PRINTF(1)("Could not fabricate Object with classID %x\n", leafClassId);
641            offset += syncDataLength;
642            continue;
643          }
644
645          if ( b->isA(CL_SYNCHRONIZEABLE) )
646          {
647            sync = dynamic_cast<Synchronizeable*>(b);
648            sync->setUniqueID( uniqueId );
649            sync->setSynchronized(true);
650 
651            PRINTF(0)("Fabricated %s with id %d\n", sync->getClassName(), sync->getUniqueID());
652          }
653          else
654          {
655            PRINTF(1)("Class with ID %x is not a synchronizeable!\n", (int)leafClassId);
656            delete b;
657            offset += syncDataLength;
658            continue;
659          }
660        }
661       
662
663        int n = sync->setStateDiff( peer->second.userId, buf+offset, syncDataLength, state, fromState ); 
664        offset += n;
665        //NETPRINTF(0)("SSSSSEEEEETTTTT: %s %d\n",sync->getClassName(), n);
666
667      }
668     
669      if ( offset != length )
670      {
671        PRINTF(0)("offset (%d) != length (%d)\n", offset, length);
672        peer->second.socket->disconnectServer();
673      }
674     
675     
676      for ( SynchronizeableList::iterator it = synchronizeables.begin(); it != synchronizeables.end(); it++ )
677      {
678        Synchronizeable & sync = **it;
679     
680        if ( !sync.beSynchronized() || sync.getUniqueID() < 0 )
681          continue;
682     
683        sync.handleRecvState( peer->second.userId, state, fromState );
684      }
685     
686      assert( peer->second.lastAckedState <= ackedState );
687      peer->second.lastAckedState = ackedState;
688     
689      assert( peer->second.lastRecvedState < state );
690      peer->second.lastRecvedState = state;
691
692    }
693 
694  }
695 
696}
697
698/**
699 * is executed when a handshake has finished
700 * @todo create playable for new user
701 */
702void NetworkStream::handleNewClient( int userId )
703{
704  MessageManager::getInstance()->initUser( userId );
705 
706  networkGameManager->signalNewPlayer( userId );
707}
708
709/**
710 * removes old items from oldSynchronizeables
711 */
712void NetworkStream::cleanUpOldSyncList( )
713{
714  int now = SDL_GetTicks();
715 
716  for ( std::map<int,int>::iterator it = oldSynchronizeables.begin(); it != oldSynchronizeables.end();  )
717  {
718    if ( it->second < now - 10*1000 )
719    {
720      std::map<int,int>::iterator delIt = it;
721      it++;
722      oldSynchronizeables.erase( delIt );
723      continue;
724    }
725    it++;
726  }
727}
728
729/**
730 * writes data to DATA/dicts/newdict
731 * @param data pointer to data
732 * @param length length
733 */
734void NetworkStream::writeToNewDict( byte * data, int length )
735{
736  if ( remainingBytesToWriteToDict <= 0 )
737    return;
738 
739  if ( length > remainingBytesToWriteToDict )
740    length = remainingBytesToWriteToDict;
741 
742  std::string fileName = ResourceManager::getInstance()->getDataDir();
743  fileName += "/dicts/newdict";
744 
745  FILE * f = fopen( fileName.c_str(), "a" );
746 
747  if ( !f )
748  {
749    PRINTF(2)("could not open %s\n", fileName.c_str());
750    remainingBytesToWriteToDict = 0;
751    return;
752  }
753 
754  if ( fwrite( data, 1, length, f ) != length )
755  {
756    PRINTF(2)("could not write to file\n");
757    fclose( f );
758    return;
759  }
760 
761  fclose( f );
762 
763  remainingBytesToWriteToDict -= length; 
764}
765
766
767
768
769
770
Note: See TracBrowser for help on using the repository browser.