2025-12-07 17:35:27 +03:00
# pragma once
# include "Helpers/Networking/NetLibrary.hpp"
2025-12-11 16:05:41 +03:00
# include "NetObjOwnerPtr.hpp"
# include "NetObjTemporaryPtr.hpp"
2025-12-13 17:56:07 +03:00
# include "NetObjWeakPtr.hpp"
2025-12-08 14:28:01 +03:00
# include "NetObjID.hpp"
2025-12-13 17:56:07 +03:00
# include <cereal/archives/portable_binary.hpp>
# include <cereal/types/unordered_set.hpp>
# include <cereal/types/vector.hpp>
2025-12-07 17:35:27 +03:00
# include "NetObjManagerTypeList.hpp"
2025-12-13 17:56:07 +03:00
# include <limits>
2025-12-30 19:46:08 +03:00
# include <list>
2025-12-07 17:35:27 +03:00
namespace NetworkingObjects {
enum class ObjPtrOrderedListCommand_StoC : uint8_t {
2025-12-30 19:46:08 +03:00
INSERT_LIST_CONSTRUCT ,
INSERT_LIST_REFERENCE ,
ERASE_ID_LIST
2025-12-07 17:35:27 +03:00
} ;
enum class ObjPtrOrderedListCommand_CtoS : uint8_t {
2025-12-30 19:46:08 +03:00
INSERT_LIST ,
ERASE_ID_LIST
2025-12-07 17:35:27 +03:00
} ;
2025-12-10 10:24:57 +03:00
template < typename T > struct NetObjOrderedListObjectInfo {
2025-12-11 16:05:41 +03:00
NetObjOrderedListObjectInfo ( NetObjOwnerPtr < T > iObj , uint32_t iPos ) :
obj ( std : : move ( iObj ) ) ,
pos ( iPos )
2025-12-10 10:24:57 +03:00
{ }
2025-12-30 19:46:08 +03:00
bool operator < ( NetObjOrderedListObjectInfo & other ) const {
return pos < other . pos ;
}
2025-12-10 10:24:57 +03:00
NetObjOrderedListObjectInfo ( ) = delete ;
2025-12-11 16:05:41 +03:00
NetObjOwnerPtr < T > obj ;
2025-12-10 10:24:57 +03:00
uint32_t pos ;
2025-12-07 17:35:27 +03:00
} ;
2025-12-30 19:46:08 +03:00
template < typename T > using NetObjOrderedListIterator = std : : list < NetObjOrderedListObjectInfo < T > > : : iterator ;
template < typename T > using NetObjOrderedListConstIterator = std : : list < NetObjOrderedListObjectInfo < T > > : : const_iterator ;
2025-12-07 17:35:27 +03:00
2025-12-30 19:46:08 +03:00
template < typename T > void set_position_obj_info_list ( NetObjOrderedListIterator < T > start , NetObjOrderedListIterator < T > end ) {
if ( start = = end )
return ;
uint32_t i = start - > pos ;
for ( ; start ! = end ; + + start ) {
start - > pos = i ;
+ + i ;
}
2025-12-07 17:35:27 +03:00
}
template < typename T > class NetObjOrderedList {
public :
2025-12-08 20:51:11 +03:00
// Don't use this function unless you're sure that class T isn't a base class
2025-12-30 19:46:08 +03:00
template < typename . . . Args > NetObjOrderedListIterator < T > emplace_back_direct ( const NetObjTemporaryPtr < NetObjOrderedList < T > > & l , Args & & . . . items ) {
std : : vector < std : : pair < NetObjOrderedListIterator < T > , NetObjOwnerPtr < T > > > newObjOwner ;
newObjOwner . emplace_back ( l - > end ( ) , std : : move ( l . get_obj_man ( ) - > template make_obj_direct < T > ( items . . . ) ) ) ;
auto toRet = l - > insert_ordered_list ( l , nullptr , newObjOwner ) ;
return toRet . empty ( ) ? l - > end ( ) : toRet . back ( ) ;
}
static NetObjOrderedListIterator < T > push_back_and_send_create ( const NetObjTemporaryPtr < NetObjOrderedList < T > > & l , T * newObj ) {
std : : vector < std : : pair < NetObjOrderedListIterator < T > , NetObjOwnerPtr < T > > > newObjOwner ;
newObjOwner . emplace_back ( l - > end ( ) , std : : move ( l . get_obj_man ( ) - > template make_obj_from_ptr < T > ( newObj ) ) ) ;
auto toRet = l - > insert_ordered_list ( l , nullptr , newObjOwner ) ;
return toRet . empty ( ) ? l - > end ( ) : toRet . back ( ) ;
}
static NetObjOrderedListIterator < T > insert_and_send_create ( const NetObjTemporaryPtr < NetObjOrderedList < T > > & l , const NetObjOrderedListIterator < T > & it , T * newObj ) {
std : : vector < std : : pair < NetObjOrderedListIterator < T > , NetObjOwnerPtr < T > > > newObjOwner ;
newObjOwner . emplace_back ( it , std : : move ( l . get_obj_man ( ) - > template make_obj_from_ptr < T > ( newObj ) ) ) ;
auto toRet = l - > insert_ordered_list ( l , nullptr , newObjOwner ) ;
return toRet . empty ( ) ? l - > end ( ) : toRet . back ( ) ;
}
static std : : vector < NetObjOrderedListIterator < T > > insert_ordered_list_and_send_create ( const NetObjTemporaryPtr < NetObjOrderedList < T > > & l , const std : : vector < std : : pair < NetObjOrderedListIterator < T > , T * > > & newObjs ) {
2025-12-13 17:56:07 +03:00
if ( newObjs . empty ( ) )
return { } ;
2025-12-30 19:46:08 +03:00
std : : vector < std : : pair < NetObjOrderedListIterator < T > , NetObjOwnerPtr < T > > > ownerPtrOrderedList ;
2025-12-13 17:56:07 +03:00
for ( uint32_t i = 0 ; i < static_cast < uint32_t > ( newObjs . size ( ) ) ; i + + )
2025-12-15 15:36:42 +03:00
ownerPtrOrderedList . emplace_back ( newObjs [ i ] . first , std : : move ( l . get_obj_man ( ) - > template make_obj_from_ptr < T > ( newObjs [ i ] . second ) ) ) ;
2025-12-13 17:56:07 +03:00
return l - > insert_ordered_list ( l , nullptr , ownerPtrOrderedList ) ;
}
2025-12-30 19:46:08 +03:00
static std : : vector < NetObjOrderedListIterator < T > > insert_ordered_list_and_send_create ( const NetObjTemporaryPtr < NetObjOrderedList < T > > & l , std : : vector < std : : pair < NetObjOrderedListIterator < T > , NetObjOwnerPtr < T > > > & newObjPtrs ) {
2025-12-22 11:13:50 +03:00
if ( newObjPtrs . empty ( ) )
return { } ;
return l - > insert_ordered_list ( l , nullptr , newObjPtrs ) ;
}
2025-12-30 19:46:08 +03:00
static void erase_list ( const NetObjTemporaryPtr < NetObjOrderedList < T > > & l , const std : : vector < NetObjOrderedListIterator < T > > & iterators , std : : vector < NetObjOwnerPtr < T > > * erasedObjects = nullptr ) {
l - > erase_it_list ( l , iterators , erasedObjects ) ;
2025-12-07 17:35:27 +03:00
}
2025-12-30 19:46:08 +03:00
static void erase ( const NetObjTemporaryPtr < NetObjOrderedList < T > > & l , const NetObjOrderedListIterator < T > & it ) {
l - > erase_it_list ( l , { it } , nullptr ) ;
2025-12-07 17:35:27 +03:00
}
2025-12-22 11:13:50 +03:00
virtual void reassign_netobj_ids_call ( ) = 0 ;
2025-12-13 21:41:32 +03:00
2025-12-30 19:46:08 +03:00
typedef std : : function < void ( const NetObjOrderedListIterator < T > & objInfo ) > InsertCallback ;
typedef std : : function < void ( const NetObjOrderedListIterator < T > & objInfo ) > EraseCallback ;
typedef std : : function < void ( const NetObjOrderedListIterator < T > & objInfo , uint32_t oldPos ) > MoveCallback ;
typedef NetObjOrderedListObjectInfo < T > value_type ;
2025-12-13 21:41:32 +03:00
void set_insert_callback ( const InsertCallback & func ) {
insertCallback = func ;
}
void set_erase_callback ( const EraseCallback & func ) {
eraseCallback = func ;
}
void set_move_callback ( const MoveCallback & func ) {
moveCallback = func ;
}
2025-12-13 17:56:07 +03:00
virtual bool contains ( const NetObjID & p ) const = 0 ;
2025-12-30 19:46:08 +03:00
virtual NetObjOrderedListIterator < T > begin ( ) = 0 ;
virtual NetObjOrderedListIterator < T > end ( ) = 0 ;
virtual NetObjOrderedListIterator < T > at ( uint32_t index ) = 0 ;
virtual NetObjOrderedListIterator < T > get ( const NetObjID & id ) = 0 ;
virtual NetObjOrderedListConstIterator < T > begin ( ) const = 0 ;
virtual NetObjOrderedListConstIterator < T > end ( ) const = 0 ;
virtual NetObjOrderedListConstIterator < T > at ( uint32_t index ) const = 0 ;
virtual NetObjOrderedListConstIterator < T > get ( const NetObjID & id ) const = 0 ;
2025-12-07 17:35:27 +03:00
virtual uint32_t size ( ) const = 0 ;
2025-12-09 19:29:58 +03:00
virtual bool empty ( ) const = 0 ;
2025-12-08 20:51:11 +03:00
virtual ~ NetObjOrderedList ( ) { }
2025-12-07 17:35:27 +03:00
protected :
2025-12-30 19:46:08 +03:00
virtual std : : vector < NetObjOrderedListIterator < T > > insert_ordered_list ( const NetObjTemporaryPtr < NetObjOrderedList < T > > & l , const std : : shared_ptr < NetServer : : ClientData > & clientInserting , std : : vector < std : : pair < NetObjOrderedListIterator < T > , NetObjOwnerPtr < T > > > & newObj ) = 0 ;
virtual void erase_it_list ( const NetObjTemporaryPtr < NetObjOrderedList < T > > & l , const std : : vector < NetObjOrderedListIterator < T > > & itList , std : : vector < NetObjOwnerPtr < T > > * erasedObjects ) = 0 ;
2025-12-11 16:05:41 +03:00
virtual void write_constructor ( const NetObjTemporaryPtr < NetObjOrderedList < T > > & l , cereal : : PortableBinaryOutputArchive & a ) = 0 ;
virtual void read_update ( const NetObjTemporaryPtr < NetObjOrderedList < T > > & l , cereal : : PortableBinaryInputArchive & a , const std : : shared_ptr < NetServer : : ClientData > & ) = 0 ;
2025-12-11 22:41:53 +03:00
virtual void read_constructor ( const NetObjTemporaryPtr < NetObjOrderedList < T > > & l , cereal : : PortableBinaryInputArchive & a , const std : : shared_ptr < NetServer : : ClientData > & ) = 0 ;
2025-12-13 21:41:32 +03:00
// Call after inserting
2025-12-30 19:46:08 +03:00
void call_insert_callback ( const NetObjOrderedListIterator < T > & objInfo ) {
2025-12-13 21:41:32 +03:00
if ( insertCallback )
insertCallback ( objInfo ) ;
}
2025-12-30 19:46:08 +03:00
void call_insert_callback_list ( const std : : vector < NetObjOrderedListIterator < T > > & objInfos ) {
2025-12-13 21:41:32 +03:00
if ( insertCallback ) {
for ( auto objInfo : objInfos )
insertCallback ( objInfo ) ;
}
}
// Call before erasing
2025-12-30 19:46:08 +03:00
void call_erase_callback ( const NetObjOrderedListIterator < T > & objInfo ) {
2025-12-13 21:41:32 +03:00
if ( eraseCallback )
eraseCallback ( objInfo ) ;
}
2025-12-30 19:46:08 +03:00
void call_move_callback ( const NetObjOrderedListIterator < T > & objInfo , uint32_t oldPos ) {
2025-12-13 21:41:32 +03:00
if ( moveCallback )
moveCallback ( objInfo , oldPos ) ;
}
2025-12-07 17:35:27 +03:00
private :
2025-12-13 21:41:32 +03:00
InsertCallback insertCallback ;
EraseCallback eraseCallback ;
MoveCallback moveCallback ;
2025-12-13 13:03:27 +03:00
template < typename S > friend void register_ordered_list_class ( NetObjManager & t ) ;
2025-12-11 16:05:41 +03:00
static void write_constructor_func ( const NetObjTemporaryPtr < NetObjOrderedList < T > > & l , cereal : : PortableBinaryOutputArchive & a ) {
2025-12-07 17:35:27 +03:00
l - > write_constructor ( l , a ) ;
}
2025-12-11 16:05:41 +03:00
static void read_constructor_func ( const NetObjTemporaryPtr < NetObjOrderedList < T > > & l , cereal : : PortableBinaryInputArchive & a , const std : : shared_ptr < NetServer : : ClientData > & c ) {
2025-12-08 22:21:07 +03:00
l - > read_constructor ( l , a , c ) ;
}
2025-12-11 16:05:41 +03:00
static void read_update_func ( const NetObjTemporaryPtr < NetObjOrderedList < T > > & l , cereal : : PortableBinaryInputArchive & a , const std : : shared_ptr < NetServer : : ClientData > & c ) {
2025-12-07 17:35:27 +03:00
l - > read_update ( l , a , c ) ;
}
} ;
template < typename T > class NetObjOrderedListServer : public NetObjOrderedList < T > {
public :
2025-12-08 20:51:11 +03:00
NetObjOrderedListServer ( ) { }
2025-12-13 17:56:07 +03:00
virtual bool contains ( const NetObjID & id ) const override {
return idToDataMap . contains ( id ) ;
2025-12-10 13:06:45 +03:00
}
2025-12-07 17:35:27 +03:00
virtual uint32_t size ( ) const override {
return data . size ( ) ;
}
2025-12-30 19:46:08 +03:00
virtual NetObjOrderedListIterator < T > begin ( ) override {
return data . begin ( ) ;
}
virtual NetObjOrderedListIterator < T > end ( ) override {
return data . end ( ) ;
}
virtual NetObjOrderedListIterator < T > at ( uint32_t index ) override {
2025-12-31 16:30:56 +03:00
if ( index > = data . size ( ) )
return data . end ( ) ;
2025-12-30 19:46:08 +03:00
return std : : next ( data . begin ( ) , index ) ;
2025-12-09 19:29:58 +03:00
}
2025-12-30 19:46:08 +03:00
virtual NetObjOrderedListIterator < T > get ( const NetObjID & id ) override {
2025-12-21 17:40:58 +03:00
auto it = idToDataMap . find ( id ) ;
if ( it = = idToDataMap . end ( ) )
2025-12-30 19:46:08 +03:00
return data . end ( ) ;
return it - > second ;
}
virtual NetObjOrderedListConstIterator < T > begin ( ) const override {
return data . begin ( ) ;
}
virtual NetObjOrderedListConstIterator < T > end ( ) const override {
return data . end ( ) ;
}
virtual NetObjOrderedListConstIterator < T > at ( uint32_t index ) const override {
2025-12-31 16:30:56 +03:00
if ( index > = data . size ( ) )
return data . end ( ) ;
2025-12-30 19:46:08 +03:00
return std : : next ( data . begin ( ) , index ) ;
}
virtual NetObjOrderedListConstIterator < T > get ( const NetObjID & id ) const override {
auto it = idToDataMap . find ( id ) ;
if ( it = = idToDataMap . end ( ) )
return data . end ( ) ;
2025-12-21 17:40:58 +03:00
return it - > second ;
}
2025-12-09 19:29:58 +03:00
virtual bool empty ( ) const override {
return data . empty ( ) ;
}
2025-12-22 11:13:50 +03:00
virtual void reassign_netobj_ids_call ( ) override {
idToDataMap . clear ( ) ;
2025-12-30 19:46:08 +03:00
for ( auto it = data . begin ( ) ; it ! = data . end ( ) ; + + it ) {
it - > obj . reassign_ids ( ) ;
idToDataMap [ it - > obj . get_net_id ( ) ] = it ;
2025-12-22 11:13:50 +03:00
}
}
2025-12-07 17:35:27 +03:00
protected :
2025-12-30 19:46:08 +03:00
virtual std : : vector < NetObjOrderedListIterator < T > > insert_ordered_list ( const NetObjTemporaryPtr < NetObjOrderedList < T > > & l , const std : : shared_ptr < NetServer : : ClientData > & clientInserting , std : : vector < std : : pair < NetObjOrderedListIterator < T > , NetObjOwnerPtr < T > > > & newObjs ) override {
if ( newObjs . empty ( ) )
return { } ;
std : : vector < NetworkingObjects : : NetObjID > idPositionList ;
for ( auto & [ itToInsertAt , newObj ] : newObjs ) {
if ( itToInsertAt = = data . begin ( ) )
idPositionList . emplace_back ( NetworkingObjects : : NetObjID { 0 , 0 } ) ;
else
idPositionList . emplace_back ( std : : prev ( itToInsertAt ) - > obj . get_net_id ( ) ) ;
}
std : : vector < NetObjOrderedListIterator < T > > toRet ;
for ( auto & [ itToInsertAt , newObj ] : newObjs ) {
auto newObjIt = data . emplace ( itToInsertAt , std : : move ( newObj ) , itToInsertAt = = data . end ( ) ? data . size ( ) : itToInsertAt - > pos ) ; // All position values will be wrong except the first one, which is all that matters
idToDataMap . emplace ( newObjIt - > obj . get_net_id ( ) , newObjIt ) ;
toRet . emplace_back ( newObjIt ) ;
2025-12-13 17:56:07 +03:00
}
2025-12-30 19:46:08 +03:00
set_position_obj_info_list < T > ( toRet [ 0 ] , data . end ( ) ) ;
l . send_server_update_to_all_clients_except ( clientInserting , RELIABLE_COMMAND_CHANNEL , [ & toRet , & idPositionList ] ( const NetObjTemporaryPtr < NetObjOrderedList < T > > & , cereal : : PortableBinaryOutputArchive & a ) {
a ( ObjPtrOrderedListCommand_StoC : : INSERT_LIST_CONSTRUCT , static_cast < uint32_t > ( toRet . size ( ) ) ) ;
for ( auto [ newObjIt , idPosition ] : std : : views : : zip ( toRet , idPositionList ) ) {
a ( idPosition ) ;
newObjIt - > obj . write_create_message ( a ) ;
2025-12-13 17:56:07 +03:00
}
} ) ;
2025-12-30 19:46:08 +03:00
l . send_server_update_to_client ( clientInserting , RELIABLE_COMMAND_CHANNEL , [ & toRet , & idPositionList ] ( const NetObjTemporaryPtr < NetObjOrderedList < T > > & , cereal : : PortableBinaryOutputArchive & a ) {
a ( ObjPtrOrderedListCommand_StoC : : INSERT_LIST_REFERENCE , static_cast < uint32_t > ( toRet . size ( ) ) ) ;
for ( auto [ newObjIt , idPosition ] : std : : views : : zip ( toRet , idPositionList ) )
a ( idPosition , newObjIt - > obj . get_net_id ( ) ) ;
2025-12-13 17:56:07 +03:00
} ) ;
2025-12-13 21:41:32 +03:00
this - > call_insert_callback_list ( toRet ) ;
2025-12-13 17:56:07 +03:00
return toRet ;
}
2025-12-30 19:46:08 +03:00
virtual void erase_it_list ( const NetObjTemporaryPtr < NetObjOrderedList < T > > & l , const std : : vector < NetObjOrderedListIterator < T > > & itList , std : : vector < NetObjOwnerPtr < T > > * erasedObjects ) {
if ( itList . empty ( ) )
2025-12-11 16:05:41 +03:00
return ;
2025-12-30 19:46:08 +03:00
std : : vector < std : : pair < uint32_t , NetObjOwnerPtr < T > > > erasedObjectsWithPos ;
NetObjOrderedListIterator < T > smallestPosIt = data . end ( ) ;
std : : vector < NetObjID > idListToSend ;
for ( auto & it : itList ) {
if ( smallestPosIt = = data . end ( ) | | smallestPosIt - > pos > = it - > pos ) {
smallestPosIt = std : : next ( it ) ;
if ( smallestPosIt ! = data . end ( ) )
smallestPosIt - > pos = it - > pos ;
}
idListToSend . emplace_back ( it - > obj . get_net_id ( ) ) ;
this - > call_erase_callback ( it ) ;
idToDataMap . erase ( it - > obj . get_net_id ( ) ) ;
2025-12-22 11:13:50 +03:00
if ( erasedObjects )
2025-12-30 19:46:08 +03:00
erasedObjectsWithPos . emplace_back ( it - > pos , std : : move ( it - > obj ) ) ;
data . erase ( it ) ;
}
if ( erasedObjects ) {
std : : sort ( erasedObjectsWithPos . begin ( ) , erasedObjectsWithPos . end ( ) , [ ] ( auto & a , auto & b ) {
return a . first < b . first ;
} ) ;
for ( auto & e : erasedObjectsWithPos )
erasedObjects - > emplace_back ( std : : move ( e . second ) ) ;
2025-12-13 21:41:32 +03:00
}
2025-12-30 19:46:08 +03:00
l . send_server_update_to_all_clients ( RELIABLE_COMMAND_CHANNEL , [ & idListToSend ] ( const NetObjTemporaryPtr < NetObjOrderedList < T > > & , cereal : : PortableBinaryOutputArchive & a ) {
a ( ObjPtrOrderedListCommand_StoC : : ERASE_ID_LIST , idListToSend ) ;
2025-12-13 17:56:07 +03:00
} ) ;
2025-12-30 19:46:08 +03:00
set_position_obj_info_list < T > ( smallestPosIt , data . end ( ) ) ;
2025-12-13 17:56:07 +03:00
}
2025-12-11 16:05:41 +03:00
virtual void write_constructor ( const NetObjTemporaryPtr < NetObjOrderedList < T > > & l , cereal : : PortableBinaryOutputArchive & a ) override {
2025-12-08 14:28:01 +03:00
a ( static_cast < uint32_t > ( data . size ( ) ) ) ;
2025-12-30 19:46:08 +03:00
for ( auto & c : data )
c . obj . write_create_message ( a ) ;
2025-12-07 17:35:27 +03:00
}
2025-12-11 22:41:53 +03:00
virtual void read_constructor ( const NetObjTemporaryPtr < NetObjOrderedList < T > > & l , cereal : : PortableBinaryInputArchive & a , const std : : shared_ptr < NetServer : : ClientData > & c ) override {
2025-12-08 14:28:01 +03:00
uint32_t constructedSize ;
a ( constructedSize ) ;
data . clear ( ) ;
2025-12-10 13:06:45 +03:00
idToDataMap . clear ( ) ;
for ( uint32_t i = 0 ; i < constructedSize ; i + + ) {
2025-12-30 19:46:08 +03:00
auto it = data . emplace ( data . end ( ) , l . get_obj_man ( ) - > template read_create_message < T > ( a , c ) , i ) ;
idToDataMap . emplace ( data . back ( ) . obj . get_net_id ( ) , it ) ;
2025-12-10 13:06:45 +03:00
}
2025-12-08 14:28:01 +03:00
// Dont send the object data to all clients in the constructor, as the function that called read_create_message is also the one that will decide where to send the data of the constructed object
// Example: the INSERT_SINGLE command in read_update, if it was inserting another NetObjOrderedList, would first construct the list using read_create_message, then it will call insert() to send it to clients
2025-12-07 17:35:27 +03:00
}
2025-12-11 16:05:41 +03:00
virtual void read_update ( const NetObjTemporaryPtr < NetObjOrderedList < T > > & l , cereal : : PortableBinaryInputArchive & a , const std : : shared_ptr < NetServer : : ClientData > & c ) override {
2025-12-07 17:35:27 +03:00
ObjPtrOrderedListCommand_CtoS command ;
a ( command ) ;
switch ( command ) {
2025-12-30 19:46:08 +03:00
case ObjPtrOrderedListCommand_CtoS : : INSERT_LIST : {
std : : vector < std : : pair < NetObjOrderedListIterator < T > , NetObjOwnerPtr < T > > > newObjs ;
2025-12-13 17:56:07 +03:00
uint32_t newObjsSize ;
a ( newObjsSize ) ;
for ( uint32_t i = 0 ; i < newObjsSize ; i + + ) {
2025-12-30 19:46:08 +03:00
NetworkingObjects : : NetObjID idBeforeElement ;
a ( idBeforeElement ) ;
NetObjOrderedListIterator < T > itToInsertAt ;
if ( idBeforeElement = = NetworkingObjects : : NetObjID { 0 , 0 } )
itToInsertAt = data . begin ( ) ;
else {
auto idToDataMapIt = idToDataMap . find ( idBeforeElement ) ;
if ( idToDataMapIt = = idToDataMap . end ( ) )
itToInsertAt = data . end ( ) ;
else
itToInsertAt = std : : next ( idToDataMapIt - > second ) ;
}
newObjs . emplace_back ( itToInsertAt , l . get_obj_man ( ) - > template read_create_message < T > ( a , c ) ) ;
2025-12-13 17:56:07 +03:00
}
insert_ordered_list ( l , c , newObjs ) ;
break ;
}
2025-12-30 19:46:08 +03:00
case ObjPtrOrderedListCommand_CtoS : : ERASE_ID_LIST : {
std : : vector < NetObjID > idsToErase ;
2025-12-13 17:56:07 +03:00
a ( idsToErase ) ;
2025-12-30 19:46:08 +03:00
std : : vector < NetObjOrderedListIterator < T > > iteratorsToErase ;
for ( auto & id : idsToErase ) {
auto it = idToDataMap . find ( id ) ;
if ( it ! = idToDataMap . end ( ) )
iteratorsToErase . emplace_back ( it - > second ) ;
}
erase_it_list ( l , iteratorsToErase , nullptr ) ;
2025-12-13 17:56:07 +03:00
break ;
}
2025-12-07 17:35:27 +03:00
}
}
private :
2025-12-30 19:46:08 +03:00
std : : list < NetObjOrderedListObjectInfo < T > > data ;
std : : unordered_map < NetObjID , NetObjOrderedListIterator < T > > idToDataMap ;
2025-12-07 17:35:27 +03:00
} ;
template < typename T > class NetObjOrderedListClient : public NetObjOrderedList < T > {
public :
2025-12-08 20:51:11 +03:00
NetObjOrderedListClient ( ) { }
2025-12-13 17:56:07 +03:00
virtual bool contains ( const NetObjID & id ) const override {
return clientIdToDataMap . contains ( id ) ;
2025-12-10 13:06:45 +03:00
}
2025-12-07 17:35:27 +03:00
virtual uint32_t size ( ) const override {
return clientData . size ( ) ;
}
2025-12-30 19:46:08 +03:00
virtual NetObjOrderedListIterator < T > begin ( ) override {
return clientData . begin ( ) ;
}
virtual NetObjOrderedListIterator < T > end ( ) override {
return clientData . end ( ) ;
2025-12-09 19:29:58 +03:00
}
2025-12-30 19:46:08 +03:00
virtual NetObjOrderedListIterator < T > at ( uint32_t index ) override {
2025-12-31 16:30:56 +03:00
if ( index > = clientData . size ( ) )
return clientData . end ( ) ;
2025-12-30 19:46:08 +03:00
return std : : next ( clientData . begin ( ) , index ) ;
}
virtual NetObjOrderedListIterator < T > get ( const NetObjID & id ) override {
auto it = clientIdToDataMap . find ( id ) ;
if ( it = = clientIdToDataMap . end ( ) )
return clientData . end ( ) ;
return it - > second ;
}
virtual NetObjOrderedListConstIterator < T > begin ( ) const override {
return clientData . begin ( ) ;
}
virtual NetObjOrderedListConstIterator < T > end ( ) const override {
return clientData . end ( ) ;
}
virtual NetObjOrderedListConstIterator < T > at ( uint32_t index ) const override {
2025-12-31 16:30:56 +03:00
if ( index > = clientData . size ( ) )
return clientData . end ( ) ;
2025-12-30 19:46:08 +03:00
return std : : next ( clientData . begin ( ) , index ) ;
}
virtual NetObjOrderedListConstIterator < T > get ( const NetObjID & id ) const override {
2025-12-21 17:40:58 +03:00
auto it = clientIdToDataMap . find ( id ) ;
if ( it = = clientIdToDataMap . end ( ) )
2025-12-30 19:46:08 +03:00
return clientData . end ( ) ;
2025-12-21 17:40:58 +03:00
return it - > second ;
}
2025-12-09 19:29:58 +03:00
virtual bool empty ( ) const override {
return clientData . empty ( ) ;
}
2025-12-22 11:13:50 +03:00
virtual void reassign_netobj_ids_call ( ) override {
serverData . clear ( ) ;
2025-12-30 19:46:08 +03:00
serverIdToDataMap . clear ( ) ;
clientIdToDataMap . clear ( ) ;
for ( auto it = clientData . begin ( ) ; it ! = clientData . end ( ) ; + + it ) {
it - > obj . reassign_ids ( ) ;
clientIdToDataMap [ it - > obj . get_net_id ( ) ] = it ;
auto serverIt = serverData . emplace ( serverData . end ( ) , it - > obj . get_net_id ( ) ) ;
serverIdToDataMap [ it - > obj . get_net_id ( ) ] = serverIt ;
2025-12-22 11:13:50 +03:00
}
}
2025-12-07 17:35:27 +03:00
protected :
2025-12-30 19:46:08 +03:00
virtual std : : vector < NetObjOrderedListIterator < T > > insert_ordered_list ( const NetObjTemporaryPtr < NetObjOrderedList < T > > & l , const std : : shared_ptr < NetServer : : ClientData > & clientInserting , std : : vector < std : : pair < NetObjOrderedListIterator < T > , NetObjOwnerPtr < T > > > & newObjs ) override {
if ( newObjs . empty ( ) )
return { } ;
std : : vector < NetworkingObjects : : NetObjID > idPositionList ;
for ( auto & [ itToInsertAt , newObj ] : newObjs ) {
if ( itToInsertAt = = clientData . begin ( ) )
idPositionList . emplace_back ( NetworkingObjects : : NetObjID { 0 , 0 } ) ;
else
idPositionList . emplace_back ( std : : prev ( itToInsertAt ) - > obj . get_net_id ( ) ) ;
2025-12-13 17:56:07 +03:00
}
2025-12-30 19:46:08 +03:00
std : : vector < NetObjOrderedListIterator < T > > toRet ;
for ( auto & [ itToInsertAt , newObj ] : newObjs ) {
auto newObjIt = clientData . emplace ( itToInsertAt , std : : move ( newObj ) , itToInsertAt = = clientData . end ( ) ? clientData . size ( ) : itToInsertAt - > pos ) ; // All position values will be wrong except the first one, which is all that matters
clientIdToDataMap . emplace ( newObjIt - > obj . get_net_id ( ) , newObjIt ) ;
toRet . emplace_back ( newObjIt ) ;
}
set_position_obj_info_list < T > ( toRet [ 0 ] , clientData . end ( ) ) ;
l . send_client_update ( RELIABLE_COMMAND_CHANNEL , [ & toRet , & idPositionList ] ( const NetObjTemporaryPtr < NetObjOrderedList < T > > & , cereal : : PortableBinaryOutputArchive & a ) {
a ( ObjPtrOrderedListCommand_CtoS : : INSERT_LIST , static_cast < uint32_t > ( toRet . size ( ) ) ) ;
for ( auto [ newObjIt , idPosition ] : std : : views : : zip ( toRet , idPositionList ) ) {
a ( idPosition ) ;
newObjIt - > obj . write_create_message ( a ) ;
2025-12-13 17:56:07 +03:00
}
} ) ;
2025-12-13 21:41:32 +03:00
this - > call_insert_callback_list ( toRet ) ;
2025-12-13 17:56:07 +03:00
return toRet ;
}
2025-12-30 19:46:08 +03:00
virtual void erase_it_list ( const NetObjTemporaryPtr < NetObjOrderedList < T > > & l , const std : : vector < NetObjOrderedListIterator < T > > & itList , std : : vector < NetObjOwnerPtr < T > > * erasedObjects ) {
if ( itList . empty ( ) )
2025-12-13 17:56:07 +03:00
return ;
2025-12-30 19:46:08 +03:00
std : : vector < std : : pair < uint32_t , NetObjOwnerPtr < T > > > erasedObjectsWithPos ;
NetObjOrderedListIterator < T > smallestPosIt = clientData . end ( ) ;
std : : vector < NetObjID > idListToSend ;
for ( auto & it : itList ) {
if ( smallestPosIt = = clientData . end ( ) | | smallestPosIt - > pos > = it - > pos ) {
smallestPosIt = std : : next ( it ) ;
if ( smallestPosIt ! = clientData . end ( ) )
smallestPosIt - > pos = it - > pos ;
}
idListToSend . emplace_back ( it - > obj . get_net_id ( ) ) ;
this - > call_erase_callback ( it ) ;
clientIdToDataMap . erase ( it - > obj . get_net_id ( ) ) ;
2025-12-22 11:13:50 +03:00
if ( erasedObjects )
2025-12-30 19:46:08 +03:00
erasedObjectsWithPos . emplace_back ( it - > pos , std : : move ( it - > obj ) ) ;
clientData . erase ( it ) ;
}
if ( erasedObjects ) {
std : : sort ( erasedObjectsWithPos . begin ( ) , erasedObjectsWithPos . end ( ) , [ ] ( auto & a , auto & b ) {
return a . first < b . first ;
} ) ;
for ( auto & e : erasedObjectsWithPos )
erasedObjects - > emplace_back ( std : : move ( e . second ) ) ;
2025-12-13 21:41:32 +03:00
}
2025-12-30 19:46:08 +03:00
l . send_client_update ( RELIABLE_COMMAND_CHANNEL , [ & idListToSend ] ( const NetObjTemporaryPtr < NetObjOrderedList < T > > & , cereal : : PortableBinaryOutputArchive & a ) {
a ( ObjPtrOrderedListCommand_CtoS : : ERASE_ID_LIST , idListToSend ) ;
2025-12-13 17:56:07 +03:00
} ) ;
2025-12-30 19:46:08 +03:00
set_position_obj_info_list < T > ( smallestPosIt , clientData . end ( ) ) ;
2025-12-13 17:56:07 +03:00
}
2025-12-11 16:05:41 +03:00
virtual void write_constructor ( const NetObjTemporaryPtr < NetObjOrderedList < T > > & l , cereal : : PortableBinaryOutputArchive & a ) override {
2025-12-08 14:28:01 +03:00
a ( static_cast < uint32_t > ( clientData . size ( ) ) ) ;
2025-12-22 11:13:50 +03:00
serverData . clear ( ) ;
2025-12-30 19:46:08 +03:00
serverIdToDataMap . clear ( ) ;
for ( auto & c : clientData ) {
c . obj . write_create_message ( a ) ;
auto serverIt = serverData . emplace ( serverData . end ( ) , c . obj . get_net_id ( ) ) ; // We are the ones who made this list, so fill up
serverIdToDataMap . emplace ( c . obj . get_net_id ( ) , serverIt ) ;
2025-12-22 11:13:50 +03:00
}
2025-12-07 17:35:27 +03:00
}
2025-12-11 22:41:53 +03:00
virtual void read_constructor ( const NetObjTemporaryPtr < NetObjOrderedList < T > > & l , cereal : : PortableBinaryInputArchive & a , const std : : shared_ptr < NetServer : : ClientData > & ) override {
2025-12-08 14:28:01 +03:00
uint32_t constructedSize ;
a ( constructedSize ) ;
clientData . clear ( ) ;
serverData . clear ( ) ;
2025-12-10 13:06:45 +03:00
clientIdToDataMap . clear ( ) ;
2025-12-30 19:46:08 +03:00
serverIdToDataMap . clear ( ) ;
2025-12-10 13:06:45 +03:00
for ( uint32_t i = 0 ; i < constructedSize ; i + + ) {
2025-12-30 19:46:08 +03:00
auto clientIt = clientData . emplace ( clientData . end ( ) , l . get_obj_man ( ) - > template read_create_message < T > ( a , nullptr ) , i ) ;
clientIdToDataMap . emplace ( clientIt - > obj . get_net_id ( ) , clientIt ) ;
auto serverIt = serverData . emplace ( serverData . end ( ) , clientIt - > obj . get_net_id ( ) ) ;
serverIdToDataMap . emplace ( clientIt - > obj . get_net_id ( ) , serverIt ) ;
2025-12-10 13:06:45 +03:00
}
2025-12-07 17:35:27 +03:00
}
2025-12-11 16:05:41 +03:00
virtual void read_update ( const NetObjTemporaryPtr < NetObjOrderedList < T > > & l , cereal : : PortableBinaryInputArchive & a , const std : : shared_ptr < NetServer : : ClientData > & ) override {
2025-12-07 17:35:27 +03:00
ObjPtrOrderedListCommand_StoC c ;
a ( c ) ;
switch ( c ) {
2025-12-30 19:46:08 +03:00
case ObjPtrOrderedListCommand_StoC : : INSERT_LIST_CONSTRUCT : {
2025-12-13 17:56:07 +03:00
uint32_t newObjsSize ;
a ( newObjsSize ) ;
2025-12-30 19:46:08 +03:00
std : : vector < NetworkingObjects : : NetObjID > idsBeforeElements ;
std : : vector < std : : pair < NetObjOrderedListIterator < T > , NetObjOwnerPtr < T > > > newObjs ;
2025-12-13 17:56:07 +03:00
for ( uint32_t i = 0 ; i < newObjsSize ; i + + ) {
2025-12-30 19:46:08 +03:00
auto & idBeforeElement = idsBeforeElements . emplace_back ( ) ;
a ( idBeforeElement ) ;
newObjs . emplace_back ( get_client_iterator_from_server_previous_net_obj_id ( idBeforeElement ) , l . get_obj_man ( ) - > template read_create_message < T > ( a , nullptr ) ) ;
2025-12-13 17:56:07 +03:00
}
2025-12-30 19:46:08 +03:00
for ( uint32_t i = 0 ; i < newObjsSize ; i + + ) {
auto serverInsertAtIt = get_server_iterator_from_server_previous_net_obj_id ( idsBeforeElements [ i ] ) ;
auto serverIt = serverData . emplace ( serverInsertAtIt , newObjs [ i ] . second . get_net_id ( ) ) ;
serverIdToDataMap . emplace ( newObjs [ i ] . second . get_net_id ( ) , serverIt ) ;
}
2025-12-30 21:18:59 +03:00
std : : vector < NetObjOrderedListIterator < T > > insertedIterators ;
2025-12-30 19:46:08 +03:00
auto firstInsertedIt = clientData . end ( ) ;
for ( uint32_t i = 0 ; i < newObjs . size ( ) ; i + + ) {
auto & [ itToInsertAt , newObj ] = newObjs [ i ] ;
auto newObjIt = clientData . emplace ( itToInsertAt , std : : move ( newObj ) , itToInsertAt = = clientData . end ( ) ? clientData . size ( ) : itToInsertAt - > pos ) ; // All position values will be wrong except the first one, which is all that matters
clientIdToDataMap . emplace ( newObjIt - > obj . get_net_id ( ) , newObjIt ) ;
2025-12-30 21:18:59 +03:00
insertedIterators . emplace_back ( newObjIt ) ;
2025-12-30 19:46:08 +03:00
if ( i = = 0 )
firstInsertedIt = newObjIt ;
}
set_position_obj_info_list < T > ( firstInsertedIt , clientData . end ( ) ) ;
2025-12-30 21:18:59 +03:00
this - > call_insert_callback_list ( insertedIterators ) ;
2025-12-30 19:46:08 +03:00
2025-12-07 17:35:27 +03:00
break ;
2025-12-09 13:00:38 +03:00
}
2025-12-30 19:46:08 +03:00
case ObjPtrOrderedListCommand_StoC : : INSERT_LIST_REFERENCE : {
2025-12-13 17:56:07 +03:00
uint32_t newObjsSize ;
a ( newObjsSize ) ;
2025-12-30 19:46:08 +03:00
struct PerElementData {
NetworkingObjects : : NetObjID idBeforeElement ;
NetworkingObjects : : NetObjID refID ;
bool foundInClient ;
NetObjOrderedListIterator < T > clientObjectIt ;
NetObjOrderedListIterator < T > clientInsertPosIt ;
} ;
std : : vector < PerElementData > receivedElements ;
2025-12-13 17:56:07 +03:00
for ( uint32_t i = 0 ; i < newObjsSize ; i + + ) {
2025-12-30 19:46:08 +03:00
auto & elemData = receivedElements . emplace_back ( ) ;
a ( elemData . idBeforeElement , elemData . refID ) ;
2025-12-13 17:56:07 +03:00
}
2025-12-30 19:46:08 +03:00
std : : list < NetObjOrderedListObjectInfo < T > > referredToObjectData ;
std : : unordered_map < NetworkingObjects : : NetObjID , uint32_t > referredToObjectOldPos ;
auto startRefreshFrom = clientData . end ( ) ;
for ( auto & elemData : receivedElements ) {
auto clientIdToDataMapIt = clientIdToDataMap . find ( elemData . refID ) ;
if ( clientIdToDataMapIt ! = clientIdToDataMap . end ( ) ) {
elemData . clientObjectIt = clientIdToDataMapIt - > second ;
referredToObjectOldPos . emplace ( elemData . clientObjectIt - > obj . get_net_id ( ) , elemData . clientObjectIt - > pos ) ;
if ( startRefreshFrom = = clientData . end ( ) | | startRefreshFrom = = elemData . clientObjectIt ) {
startRefreshFrom = std : : next ( elemData . clientObjectIt ) ;
if ( startRefreshFrom ! = clientData . end ( ) )
startRefreshFrom - > pos = elemData . clientObjectIt - > pos ;
}
referredToObjectData . splice ( referredToObjectData . end ( ) , clientData , elemData . clientObjectIt , std : : next ( elemData . clientObjectIt ) ) ;
clientIdToDataMap . erase ( clientIdToDataMapIt ) ;
elemData . foundInClient = true ;
}
else
elemData . foundInClient = false ;
}
set_position_obj_info_list < T > ( startRefreshFrom , clientData . end ( ) ) ;
for ( auto & elemData : receivedElements ) {
if ( elemData . foundInClient )
elemData . clientInsertPosIt = get_client_iterator_from_server_previous_net_obj_id ( elemData . idBeforeElement ) ;
}
for ( auto & elemData : receivedElements ) {
auto serverInsertAtIt = get_server_iterator_from_server_previous_net_obj_id ( elemData . idBeforeElement ) ;
auto serverIt = serverData . emplace ( serverInsertAtIt , elemData . refID ) ;
serverIdToDataMap . emplace ( elemData . refID , serverIt ) ;
}
auto firstInsertedIt = clientData . end ( ) ;
uint32_t firstInsertedActualPos = 0 ;
for ( auto & elemData : receivedElements ) {
if ( ! elemData . foundInClient )
continue ;
if ( firstInsertedIt = = clientData . end ( ) ) {
firstInsertedActualPos = elemData . clientInsertPosIt - > pos ;
firstInsertedIt = elemData . clientObjectIt ;
}
clientData . splice ( elemData . clientInsertPosIt , referredToObjectData , elemData . clientObjectIt , std : : next ( elemData . clientObjectIt ) ) ;
clientIdToDataMap . emplace ( elemData . clientObjectIt - > obj . get_net_id ( ) , elemData . clientObjectIt ) ;
}
// Instead of set_position_obj_info_list<T>
if ( firstInsertedIt ! = clientData . end ( ) ) {
auto & it = firstInsertedIt ;
auto & actualPos = firstInsertedActualPos ;
for ( ; it ! = clientData . end ( ) ; + + it ) {
it - > pos = actualPos ;
auto referredToObjectOldPosIt = referredToObjectOldPos . find ( it - > obj . get_net_id ( ) ) ;
if ( referredToObjectOldPosIt ! = referredToObjectOldPos . end ( ) ) {
uint32_t oldPos = referredToObjectOldPosIt - > second ;
if ( oldPos ! = it - > pos )
this - > call_move_callback ( it , oldPos ) ;
}
actualPos + + ;
}
2025-12-11 16:05:41 +03:00
}
2025-12-07 17:35:27 +03:00
break ;
2025-12-09 13:00:38 +03:00
}
2025-12-30 19:46:08 +03:00
case ObjPtrOrderedListCommand_StoC : : ERASE_ID_LIST : {
std : : vector < NetworkingObjects : : NetObjID > idsToErase ;
a ( idsToErase ) ;
for ( auto & id : idsToErase ) {
auto serverIDToDataMapIt = serverIdToDataMap . find ( id ) ;
serverData . erase ( serverIDToDataMapIt - > second ) ;
serverIdToDataMap . erase ( serverIDToDataMapIt ) ;
2025-12-13 17:56:07 +03:00
}
2025-12-30 19:46:08 +03:00
NetObjOrderedListIterator < T > smallestPosIt = clientData . end ( ) ;
for ( auto & id : idsToErase ) {
auto clientIt = clientIdToDataMap . find ( id ) ;
if ( clientIt = = clientIdToDataMap . end ( ) )
continue ;
auto it = clientIt - > second ;
if ( smallestPosIt = = clientData . end ( ) | | smallestPosIt - > pos > = it - > pos ) {
smallestPosIt = std : : next ( it ) ;
if ( smallestPosIt ! = clientData . end ( ) )
smallestPosIt - > pos = it - > pos ;
}
this - > call_erase_callback ( it ) ;
clientIdToDataMap . erase ( clientIt ) ;
clientData . erase ( it ) ;
2025-12-13 21:41:32 +03:00
}
2025-12-30 19:46:08 +03:00
set_position_obj_info_list < T > ( smallestPosIt , clientData . end ( ) ) ;
2025-12-13 17:56:07 +03:00
break ;
}
2025-12-07 17:35:27 +03:00
}
}
private :
2025-12-30 19:46:08 +03:00
NetworkingObjects : : NetObjOrderedListIterator < T > get_client_iterator_from_server_previous_net_obj_id ( NetworkingObjects : : NetObjID previousObjId ) {
if ( previousObjId = = NetworkingObjects : : NetObjID { 0 , 0 } )
return clientData . begin ( ) ;
else {
auto serverIt = serverIdToDataMap [ previousObjId ] ;
for ( ; ; ) {
NetworkingObjects : : NetObjID netObjID = * serverIt ;
auto clientIt = clientIdToDataMap . find ( netObjID ) ;
if ( clientIt ! = clientIdToDataMap . end ( ) )
return std : : next ( clientIt - > second ) ;
if ( serverIt = = serverData . begin ( ) )
return clientData . begin ( ) ;
- - serverIt ;
2025-12-09 13:00:38 +03:00
}
2025-12-11 16:05:41 +03:00
}
2025-12-30 19:46:08 +03:00
return clientData . begin ( ) ;
}
2025-12-11 16:05:41 +03:00
2025-12-30 19:46:08 +03:00
NetworkingObjects : : NetObjOrderedListIterator < T > get_prev_client_iterator_from_server_previous_net_obj_id ( NetworkingObjects : : NetObjID previousObjId ) {
if ( previousObjId = = NetworkingObjects : : NetObjID { 0 , 0 } )
return clientData . begin ( ) ;
else {
auto serverIt = serverIdToDataMap [ previousObjId ] ;
for ( ; ; ) {
NetworkingObjects : : NetObjID netObjID = * serverIt ;
auto clientIt = clientIdToDataMap . find ( netObjID ) ;
if ( clientIt ! = clientIdToDataMap . end ( ) )
return clientIt - > second ;
if ( serverIt = = serverData . begin ( ) )
return clientData . begin ( ) ;
- - serverIt ;
2025-12-28 17:34:33 +03:00
}
}
2025-12-30 19:46:08 +03:00
return clientData . begin ( ) ;
}
std : : list < NetObjID > : : iterator get_server_iterator_from_server_previous_net_obj_id ( NetworkingObjects : : NetObjID previousObjId ) {
if ( previousObjId = = NetworkingObjects : : NetObjID { 0 , 0 } )
return serverData . begin ( ) ;
return std : : next ( serverIdToDataMap [ previousObjId ] ) ;
2025-12-09 13:00:38 +03:00
}
2025-12-30 19:46:08 +03:00
std : : list < NetObjID > serverData ;
std : : unordered_map < NetObjID , std : : list < NetObjID > : : iterator > serverIdToDataMap ;
std : : list < NetObjOrderedListObjectInfo < T > > clientData ;
std : : unordered_map < NetObjID , NetObjOrderedListIterator < T > > clientIdToDataMap ;
2025-12-07 17:35:27 +03:00
} ;
2025-12-13 13:03:27 +03:00
template < typename S > void register_ordered_list_class ( NetObjManager & objMan ) {
objMan . register_class < NetObjOrderedList < S > , NetObjOrderedList < S > , NetObjOrderedListClient < S > , NetObjOrderedListServer < S > > ( {
2025-12-07 17:35:27 +03:00
. writeConstructorFuncClient = NetObjOrderedList < S > : : write_constructor_func ,
. readConstructorFuncClient = NetObjOrderedList < S > : : read_constructor_func ,
. readUpdateFuncClient = NetObjOrderedList < S > : : read_update_func ,
. writeConstructorFuncServer = NetObjOrderedList < S > : : write_constructor_func ,
. readConstructorFuncServer = NetObjOrderedList < S > : : read_constructor_func ,
. readUpdateFuncServer = NetObjOrderedList < S > : : read_update_func ,
} ) ;
}
}