Files
RP412/MUNGA/ENTITY.cpp
T
CydandClaude Opus 5 adcb81e7fc Replicants interpolate too
The first cut hung the snapshot off Mover::BeginStep, which is inside
Entity::PerformAndWatch's fixed-step interleave - and that interleave sits
entirely inside "if (GetInstance() != ReplicantInstance)". A replicant
never runs it; it reaches the step loop through Simulation::PerformAndWatch
instead. Every remote pod is a replicant, so on a Live Cam the camera was
being interpolated while the car it was watching still stepped. Smoother,
and most of the way to nowhere - which is exactly what "still some
hitching" was.

So the hooks move to Simulation::PerformTo, where both paths meet:
SnapshotRenderOrigin before each Perform, SetRenderStepFraction after the
loop, two virtuals that do nothing by default and are overridden by Entity
because Entity owns the origin. Mover::BeginStep goes back to what it was,
so there is now one mechanism instead of two.

Taking the snapshot inside the step loop is also strictly better placed
than BeginStep was: it lands immediately before the integration, and still
after any BeginStep teleport, so a VTV's scheduled respawn stays a cut.

Entity::PerformAndWatch keeps computing the fraction itself after its
interleave, because there PerformTo is called once per step with a till
one step ahead and so sees no leftover at all - it needs the FRAME's till,
which only the interleave has.

Determinism re-proved, and more thoroughly than the first time. The
scripted lap at 240 fps, interpolation on and off, on both the old build
and this one: all four runs agree to the last decimal at the same
simulation time - pos -15.06739 3.01541 388.02603 at t=15.260. The one
"differing" sample in the raw comparison was the trace sampling at t=1.260
in one run and t=1.280 in the other and then realigning, not divergence.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-10 23:36:44 -05:00

1784 lines
44 KiB
C++

#include "munga.h"
#pragma hdrstop
#include "entity.h"
#include "interest.h"
#include "exptbl.h"
#include "hostmgr.h"
#include "player.h"
#include "audio.h"
#include "fileutil.h"
#include "registry.h"
#include "event.h"
#include "app.h"
#include "nttmgr.h"
#include "subsystm.h"
#include "namelist.h"
#include "notation.h"
#if defined(TRACE_EXECUTE_ENTITY)
static BitTrace Execute_Entity("Execute Entity");
#define SET_EXECUTE_ENTITY() Execute_Entity.Set()
#define CLEAR_EXECUTE_ENTITY() Execute_Entity.Clear()
#else
#define SET_EXECUTE_ENTITY()
#define CLEAR_EXECUTE_ENTITY()
#endif
#if defined(TRACE_PERFORM_ENTITY)
static BitTrace Perform_Entity("Perform Entity");
#define SET_PERFORM_ENTITY() Perform_Entity.Set()
#define CLEAR_PERFORM_ENTITY() Perform_Entity.Clear()
#else
#define SET_PERFORM_ENTITY()
#define CLEAR_PERFORM_ENTITY()
#endif
#if defined(TRACE_PERFORM_SUBSYSTEMS)
static BitTrace Perform_Subsystems("Perform Subsystems");
#define SET_PERFORM_SUBSYSTEMS() Perform_Subsystems.Set()
#define CLEAR_PERFORM_SUBSYSTEMS() Perform_Subsystems.Clear()
#else
#define SET_PERFORM_SUBSYSTEMS()
#define CLEAR_PERFORM_SUBSYSTEMS()
#endif
//#############################################################################
//############################## Entity #################################
//#############################################################################
//#############################################################################
// Virtual Data support
//
Derivation* Entity::GetClassDerivations()
{
static Derivation classDerivations(Simulation::GetClassDerivations(), "Entity");
return &classDerivations;
}
Entity::SharedData
Entity::DefaultData(
Entity::GetClassDerivations(),
Entity::GetMessageHandlers(),
Entity::GetAttributeIndex(),
Entity::StateCount,
Entity::Make
);
//#############################################################################
// Message Support
//
const Receiver::HandlerEntry
Entity::MessageHandlerEntries[]=
{
{
Entity::MakeMessageID,
"Make",
(Entity::Handler)&Entity::DefaultMessageHandler
},
{
Entity::MakeReadyMessageID,
"MakeReady",
(Entity::Handler)&Entity::DefaultMessageHandler
},
{
Entity::RemakeEntityMessageID,
"RemakeEntity",
(Entity::Handler)&Entity::DefaultMessageHandler
},
{
Entity::RemakeReadyMessageID,
"RemakeReady",
(Entity::Handler)&Entity::DefaultMessageHandler
},
{
Entity::RemakeCompleteMessageID,
"RemakeComplete",
(Entity::Handler)&Entity::DefaultMessageHandler
},
{
Entity::RemakeIncompleteMessageID,
"RemakeIncomplete",
(Entity::Handler)&Entity::DefaultMessageHandler
},
MESSAGE_ENTRY(Entity, DestroyEntity),
{
Entity::TransferEntityMessageID,
"TransferEntity",
(Entity::Handler)&Entity::DefaultMessageHandler
},
{
Entity::TransferCompleteMessageID,
"TransferComplete",
(Entity::Handler)&Entity::DefaultMessageHandler
},
{
Entity::TransferIncompleteMessageID,
"TransferIncomplete",
(Entity::Handler)&Entity::DefaultMessageHandler
},
MESSAGE_ENTRY(Entity, BecomeInteresting),
MESSAGE_ENTRY(Entity, BecomeUninteresting),
MESSAGE_ENTRY(Entity, Update),
{
Entity::SubscribeReplicantMessageID,
"SubscribeReplicant",
(Entity::Handler)&Entity::DefaultMessageHandler
},
{
Entity::UnsubscribeReplicantMessageID,
"UnsubscribeReplicant",
(Entity::Handler)&Entity::DefaultMessageHandler
},
MESSAGE_ENTRY(Entity, TakeDamage),
MESSAGE_ENTRY(Entity, PlayerLink),
MESSAGE_ENTRY(Entity, TakeDamageStream)
};
Entity::MessageHandlerSet& Entity::GetMessageHandlers()
{
static Entity::MessageHandlerSet messageHandlers(ELEMENTS(Entity::MessageHandlerEntries), Entity::MessageHandlerEntries, Receiver::GetMessageHandlers());
return messageHandlers;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Entity::Receive(Event* event)
{
Check(this);
Check(event);
//
//-------------------------------------------------------------------------
// If the entity is being transferred, post the message to the new owner of
// the entity
//-------------------------------------------------------------------------
//
//
//--------------------------------------------------------------------------
// If the object is not valid, filter the message to determine which ones to
// defer and which ones to immediately process
//--------------------------------------------------------------------------
//
if (!IsValid())
{
//switch case commented out by Ryan Bunker on 1/6/07
//switch (event->messageToSend->messageID)
{
//
//----------------------------------------------------------------------
// The messages which should be allowed through should just break out of
// this switch
//----------------------------------------------------------------------
//
//default:
//
//-------------------
// Defer the messages
//-------------------
//
event->Defer();
return;
}
}
//
//------------------------------------------------
// Go ahead and dispatch the message to the entity
//------------------------------------------------
//
Receiver::Receive(event);
#if defined(USE_FPU_ANALYSIS)
if (!Fpu_Ok())
{
Registry *registry = application->GetRegistry();
Receiver::SharedData *shared_data =
registry->GetStaticData(GetClassID());
Derivation *derivation = shared_data->derivedClasses;
Receiver::MessageHandlerSet *handlers =
shared_data->activeMessageHandlers;
DEBUG_STREAM << derivation->className << ',' << GetInstance() << endl << std::flush;
DEBUG_STREAM << handlers->GetName(event->messageToSend->messageID)
<< endl;
Fail("Event corrupted FPU!");
}
#else
Check_Fpu();
#endif
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Entity::Dispatch(Receiver::Message* incoming)
{
Message *message = (Message*) incoming;
Check(this);
Check(message);
//
// Call Receiver if it is not entity aware!!!!!
//
if (message->messageID < Receiver::NextMessageID)
{
Receiver::Dispatch(message);
}
//
//-----------------------------------------
// Initialize the entity information fields
//-----------------------------------------
//
message->entityID = entityID;
message->interestZoneID = interestZoneID;
//
//-------------------------------------------------------------------
// If this is a replicant instance, reroute the message to the master
//-------------------------------------------------------------------
//
if (GetInstance() == ReplicantInstance)
{
application->SendMessage(
ownerID,
EntityManager::EntityManagerClientID,
message
);
}
//
//--------------------------------------------------------
// If this entity is invalid, post the message as an event
//--------------------------------------------------------
//
else if (!IsValid())
{
// ECH 1/28/96 - application->Post(DefaultEventPriority, this, message);
application->Post(EntityInvalidEventPriority, this, message);
}
//
//--------------------------------------------
// Otherwise, go ahead and receive the message
//--------------------------------------------
//
else
{
Receiver::Receive(message);
}
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Entity::DispatchToReplicants(Message *what)
{
Check(this);
Check(what);
what->entityID = entityID;
what->interestZoneID = interestZoneID;
Check(application);
InterestManager *int_mgr = application->GetInterestManager();
Check(int_mgr);
int_mgr->EntityBroadcastToReplicants(this, what);
Check_Fpu();
}
//#############################################################################
// Attribute Support
//
const Entity::IndexEntry
Entity::AttributePointers[]=
{
ATTRIBUTE_ENTRY(Entity, LocalToWorld, localToWorld),
ATTRIBUTE_ENTRY(Entity, LocalOrigin, localOrigin),
ATTRIBUTE_ENTRY(Entity, DamageZoneCount, damageZoneCount),
ATTRIBUTE_ENTRY(Entity, DamageZones, damageZones)
};
Entity::AttributeIndexSet& Entity::GetAttributeIndex()
{
static Entity::AttributeIndexSet attributeIndex(ELEMENTS(Entity::AttributePointers),
Entity::AttributePointers,
Simulation::GetAttributeIndex()
);
return attributeIndex;
}
//#############################################################################
// Model Support
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Entity::WriteUpdateRecord(
Simulation::UpdateRecord *record,
int update_model
)
{
Check(this);
Check_Pointer(record);
switch (update_model)
{
case DefaultUpdateModelBit:
{
Simulation::WriteUpdateRecord(record, update_model);
//
//----------------------------
// Write out the record update
//----------------------------
//
UpdateRecord *update = (UpdateRecord*)record;
update->localOrigin = localOrigin;
updateOrigin = localOrigin;
update->recordLength = sizeof(*update);
}
break;
case DamageZoneUpdateModelBit:
record->timeStamp = lastPerformance;
record->recordID = (Word)update_model;
WriteDamageUpdateRecord(record);
break;
default:
Simulation::WriteUpdateRecord(record, update_model);
break;
}
record->subsystemID = (Word)(EntitySubsystemID + 1);
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Entity::UpdateMessageHandler(UpdateMessage *message)
{
Check(this);
Check(message);
//
//------------------------------------------------------
// Position to the beginning of the update record blocks
//------------------------------------------------------
//
MemoryStream stream(message, message->messageLength, sizeof(*message));
//
//------------------------------------------------------------------------
// Step through each block until there are no more remaining, and send the
// update out the the simulation indicated by the subsystemID
//
// This is the only point on the receive path that knows WHOSE update
// this is - the records themselves carry a timestamp but not an owner -
// so the sender is published here for the net clock to align against.
// Every record in the message, and the damage zones nested inside them,
// came from the same machine in the same frame.
//------------------------------------------------------------------------
//
//
// Only for an entity somebody else owns. Our own clock needs no
// aligning, and an update we somehow handed ourselves would otherwise
// drag lastUpdate back by a frame for no reason.
//
Check(application);
Check(application->GetHostManager());
Logical remote_owner =
GetOwnerID() != application->GetHostManager()->GetLocalHostID();
if (remote_owner)
{
NetClock_BeginUpdate(GetOwnerID());
}
while (stream.GetBytesRemaining())
{
Simulation::UpdateRecord *update =
(Simulation::UpdateRecord*)stream.GetPointer();
Check_Pointer(update);
Simulation *simulation = GetSimulation((int)update->subsystemID-1);
Check(simulation);
simulation->ReadUpdateRecord(update);
stream.AdvancePointer(update->recordLength);
}
if (remote_owner)
{
NetClock_EndUpdate();
}
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Entity::ReadUpdateRecord(Simulation::UpdateRecord *record)
{
Check(this);
Check_Pointer(record);
switch(record->recordID)
{
case DefaultUpdateModelBit:
{
//
//-----------------------
// Read the record update
//-----------------------
//
UpdateRecord *update = (UpdateRecord*)record;
updateOrigin = update->localOrigin;
//
//------------------------------------------------------------------
// If we aren't able to run yet, we must update local origin as well
//------------------------------------------------------------------
//
Check(application);
int app_state = application->GetApplicationState();
if (
app_state < Application::RunningMission
|| app_state == Application::CreatingMission
)
{
localOrigin = updateOrigin;
localToWorld = localOrigin;
}
Simulation::ReadUpdateRecord(record);
}
break;
case DamageZoneUpdateModelBit:
ReadDamageUpdateRecord(record);
break;
default:
Simulation::ReadUpdateRecord(record);
}
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Entity::ReadDamageUpdateRecord(Simulation::UpdateRecord *update_record)
{
//
//------------------------------------------------------------------------
// Step through each block until there are no more remaining, and send the
// update out the the damageZone indicated by the damageZoneIndex
//------------------------------------------------------------------------
//
MemoryStream stream(
update_record,
update_record->recordLength,
sizeof(*update_record)
);
while (stream.GetBytesRemaining())
{
DamageZone::UpdateRecord *damage_zone_record =
(DamageZone::UpdateRecord*)stream.GetPointer();
int damage_zone_index = damage_zone_record->damageZoneIndex;
damageZones[damage_zone_index]->ReadUpdateRecord(damage_zone_record);
stream.AdvancePointer(damage_zone_record->recordLength);
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
#define MAX_UPDATE_LENGTH 1400
static long Update_Buffer[MAX_UPDATE_LENGTH >> 2];
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Entity::WriteDamageUpdateRecord(Simulation::UpdateRecord *update_record)
{
Check(this);
Check_Pointer(update_record);
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Make a memory stream this size of a network packet
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
MemoryStream stream(
update_record,
sizeof(Update_Buffer) - ((char*)update_record - (char*)Update_Buffer),
sizeof(*update_record)
);
DamageZone::UpdateRecord *record_start =
(DamageZone::UpdateRecord*)stream.GetPointer();
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Update Every DamageZone For NOW
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
for(int ii=0;ii<damageZoneCount;++ii)
{
DamageZone::UpdateRecord *damage_zone_update =
(DamageZone::UpdateRecord*)stream.GetPointer();
damageZones[ii]->WriteUpdateRecord(damage_zone_update);
stream.AdvancePointer(damage_zone_update->recordLength);
}
//
//-----------------------------------------------
// Check to see if an update message is necessary
//-----------------------------------------------
//
if (stream.GetPointer() != record_start)
{
Simulation::UpdateRecord *damage_update =
(Simulation::UpdateRecord*)update_record;
damage_update->recordLength =
(char*)stream.GetPointer() - (char*)update_record;
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Entity::UpdateMessage*
Entity::Execute(const Time& till)
{
SET_EXECUTE_ENTITY();
Check(this);
Check(&till);
MemoryStream
stream(Update_Buffer, sizeof(Update_Buffer), sizeof(UpdateMessage));
char *record_start = (char*)stream.GetPointer();
//
//-----------------------------------------------------------------------
// Check to see if the application will let us run. If We are starting a
// mission, only the player class objects can execute
//-----------------------------------------------------------------------
//
Check(application);
if (
application->GetApplicationState() == Application::RunningMission
|| application->GetApplicationState() == Application::EndingMission
|| IsPreRunnable()
)
{
PerformAndWatch(till, &stream);
}
else
{
WriteSimulationUpdate(&stream);
}
#if defined(USE_FPU_ANALYSIS)
if (!Fpu_Ok())
{
Registry *registry = application->GetRegistry();
Receiver::SharedData *shared_data =
registry->GetStaticData(GetClassID());
Derivation *derivation = shared_data->derivedClasses;
DEBUG_STREAM << derivation->className << ',' << GetInstance() << endl << std::flush;
Fail("Simulation corrupted FPU!");
}
#endif
//
//-----------------------------------------------
// Check to see if an update message is necessary
//-----------------------------------------------
//
if (stream.GetPointer() != record_start)
{
UpdateMessage *update = (UpdateMessage*)Update_Buffer;
update->messageID = UpdateMessageID;
update->messageLength = (char*)stream.GetPointer() - (char*)Update_Buffer;
update->messageFlags = 0;
CLEAR_EXECUTE_ENTITY();
return update;
}
//
//-------------------------------------
// Otherwise, return NULL for no update
//-------------------------------------
//
Check_Fpu();
CLEAR_EXECUTE_ENTITY();
return NULL;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Simulation*
Entity::GetSimulation(int index)
{
Check(this);
if (index == EntitySubsystemID)
{
return this;
}
Check_Fpu();
if ((unsigned)index < subsystemCount)
{
return subsystemArray[index];
}
else
{
return NULL;
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Subsystem*
Entity::FindSubsystem(const char* name)
{
Check(this);
Check_Pointer(name);
for (int i=0; i<subsystemCount; ++i)
{
if (subsystemArray[i])
{
Check(subsystemArray[i]);
if (!stricmp(subsystemArray[i]->GetName(), name))
{
return subsystemArray[i];
}
}
}
Check_Fpu();
return NULL;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
#define MAX_SUB_FILE_NAME (256)
int
Entity::FindSubsytemID
(
#if DEBUG_LEVEL>0
const char *model_name,
const char *subsystem_name
#else
const char *,
const char *subsystem_name
#endif
)
{
Check_Pointer(model_name);
Check_Pointer(subsystem_name);
int subsytem_id = EntitySubsystemID;
int sub_count = 0;
CString sub_file_string;
CString sub_directory_string("models\\");
CString model_name_string("model_name");
CString sub_post_string(".sub");
sub_file_string =
sub_directory_string + model_name_string + sub_post_string;
NotationFile sub_file(sub_file_string);
Check(&sub_file);
NameList *page_list = sub_file.MakePageList();
Register_Object(page_list);
NameList::Entry *entry=page_list->GetFirstEntry();
while (entry != NULL)
{
Check(entry);
Check_Pointer(entry->GetName());
if (!stricmp(subsystem_name, entry->GetName()))
{
subsytem_id = sub_count;
break;
}
++sub_count;
entry = entry->GetNextEntry();
}
Unregister_Object(page_list);
delete page_list;
return subsytem_id;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Entity::PerformAndWatch(
const Time &till,
MemoryStream *update_stream
)
{
Check(this);
Check(&till);
//
//-----------------------------------------
// Execute all the subsystems, then ourself
//-----------------------------------------
//
SET_PERFORM_SUBSYSTEMS();
if (subsystemArray)
{
Check_Pointer(subsystemArray);
//
//---------------------------------------------------
// If this is not a replicant, execute all subsystems
//---------------------------------------------------
//
if (GetInstance() != ReplicantInstance)
{
//
//----------------------------------------------------------------
// Fixed-step: the subsystems and the entity advance TOGETHER,
// one step at a time, because they read each other mid-flight.
// The VTV's hover spring is computed from its thrusters'
// measured heights, and each thruster measures from where the
// vehicle IS - so thrusters stepped twice against a vehicle
// that has not moved yet hand back two identical height
// samples, and the spring fires twice on stale data. Measured,
// that pod climbs at 30 fps and flies level at 144.
//
// So the step loop lives HERE, above both: everyone is walked
// to the same sub-frame instant before anyone takes the next
// step. Watchers and the update stream still run once per
// frame, after the loop - stepping is physics, watching is
// I/O, and only the first belongs inside.
//
// The interleave keys off the ENTITY's own clock so a
// subsystem created mid-flight (they are made alongside their
// owner) can never wedge the loop.
//----------------------------------------------------------------
//
Scalar fixed_step = Simulation::FixedStep();
if (fixed_step > (Scalar) 0)
{
//
// One grid for the whole vehicle. Every Simulation anchors
// its own lastPerformance at its creation time, so an
// entity and its subsystems were stepping on grids offset
// by a random fraction of a step - deterministic within a
// run, DIFFERENT between runs, because creation times ride
// on load timing. The thrusters' measurements then landed
// a different sub-step distance from the vehicle's
// integration every launch, which is physics drift no seed
// can pin. Snap the subsystems onto the entity's grid; the
// interleave below then keeps everyone in lockstep by
// construction, and once aligned this assignment is a
// no-op every frame after.
//
for (int i=0; i<subsystemCount; ++i)
{
if (subsystemArray[i] &&
subsystemArray[i]->IsNonReplicantExecutable())
{
subsystemArray[i]->SetLastPerformance(
GetLastPerformance());
}
}
Time step_till = GetLastPerformance();
step_till += fixed_step;
while (step_till <= till)
{
//
// BeginStep on the ENTITY comes before the subsystems
// perform: the Mover's force accumulator is cleared
// here, and the thrusters then ADD this step's forces
// into a clean slate. The first version left that
// clear on the per-frame path, so a two-step frame
// integrated step one's thrust twice - and since how
// many steps land in a frame rides on wall-clock
// jitter, no two runs saw the same force history.
// Identical configs measured 0.23 apart because of it.
//
BeginStep();
for (int i=0; i<subsystemCount; ++i)
{
if (subsystemArray[i] &&
subsystemArray[i]->IsNonReplicantExecutable())
{
subsystemArray[i]->BeginStep();
subsystemArray[i]->PerformTo(step_till);
}
}
Simulation::PerformTo(step_till);
step_till += fixed_step;
}
//
// How far past the last completed step the frame we are
// about to draw falls, as a fraction of one step. This is
// the leftover the fixed-step loop deliberately does not
// simulate - see renderPreviousOrigin in entity.h.
//
{
Scalar leftover = till - GetLastPerformance();
Scalar fraction = (fixed_step > (Scalar) 0)
? (leftover / fixed_step) : (Scalar) 0;
if (fraction < (Scalar) 0) fraction = (Scalar) 0;
if (fraction > (Scalar) 1) fraction = (Scalar) 1;
renderStepFraction = fraction;
}
for (int i=0; i<subsystemCount; ++i)
{
if (subsystemArray[i] &&
subsystemArray[i]->IsNonReplicantExecutable())
{
subsystemArray[i]->WatchAndWrite(update_stream);
}
}
SET_PERFORM_ENTITY();
Simulation::WatchAndWrite(update_stream);
Check_Fpu();
CLEAR_PERFORM_ENTITY();
CLEAR_PERFORM_SUBSYSTEMS();
return;
}
for (int i=0; i<subsystemCount; ++i)
{
if (subsystemArray[i])
{
Subsystem *subsystem = subsystemArray[i];
Check(subsystem);
if (subsystem->IsNonReplicantExecutable())
{
subsystem->PerformAndWatch(till, update_stream);
#if defined(USE_FPU_ANALYSIS)
if (!Fpu_Ok())
{
Registry *registry = application->GetRegistry();
Receiver::SharedData *shared_data =
registry->GetStaticData(GetClassID());
Derivation *derivation = shared_data->derivedClasses;
DEBUG_STREAM << derivation->className
<< "\nExecuting subsystem[" << i << "], "
<< subsystem->GetName() << endl;
Fail("Simulation corrupted FPU!");
}
#endif
}
}
}
}
//
//--------------------------------------------------------------------
// If this is a replicant, execute only those subsystems which need to
//--------------------------------------------------------------------
//
else
{
for (int i=0; i<subsystemCount; ++i)
{
if (subsystemArray[i])
{
Subsystem *subsystem = subsystemArray[i];
Check(subsystem);
if (subsystem->IsReplicantExecutable())
{
subsystem->PerformAndWatch(till, update_stream);
#if defined(USE_FPU_ANALYSIS)
if (!Fpu_Ok())
{
Registry *registry = application->GetRegistry();
Receiver::SharedData *shared_data =
registry->GetStaticData(GetClassID());
Derivation *derivation = shared_data->derivedClasses;
DEBUG_STREAM << derivation->className
<< " replicant\nExecuting subsystem[" << i << "], "
<< subsystem->GetName() << endl;
Fail("Simulation corrupted FPU!");
}
#endif
}
}
}
}
}
CLEAR_PERFORM_SUBSYSTEMS();
SET_PERFORM_ENTITY();
Simulation::PerformAndWatch(till, update_stream);
Check_Fpu();
CLEAR_PERFORM_ENTITY();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Entity::PlayerLinkMessageHandler(PlayerLinkMessage *message)
{
Check(application);
HostManager *host = application->GetHostManager();
Check(host);
playerLink =(Player*) host->GetEntityPointer(message->playerID);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Entity::TakeDamageStreamMessageHandler(TakeDamageStreamMessage *message)
{
Check(this);
Check(message);
//
//-------------------------------------------------
// Extract the common information out of the stream
//-------------------------------------------------
//
Damage damage_data;
EntityID shooting_entity = message->shootingEntity;
int damage_zone_index = message->damageZoneIndex;
damage_data.impactPoint = message->impactPoint;
//
//---------------------------------------------------
// Figure out how many damage messages there are here
//---------------------------------------------------
//
DynamicMessage damage_stream(
message,
message->messageLength,
sizeof(*message)
);
int entry_count;
damage_stream >> entry_count;
for (int i=0; i<entry_count; ++i)
{
//
//-------------------------------------------------
// Read in damageType, damageAmount and subsystemID
//-------------------------------------------------
//
damage_stream >> damage_data.damageType >> damage_data.damageAmount;
int subsystem;
damage_stream >> subsystem;
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Apply the damage to the appropriate damageZone
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
TakeDamageMessage
message(
TakeDamageMessageID,
sizeof(TakeDamageMessage),
shooting_entity,
damage_zone_index,
damage_data,
subsystem
);
Dispatch(&message);
}
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Entity::TakeDamageMessageHandler(TakeDamageMessage *message)
{
//
//-------------------------------------
// YOU MUST CHECK FOR -1 DamageZone!!!!
//-------------------------------------
//
if (!damageZones || message->damageZone == -1)
{
return;
}
//
//----------------------------------------------------------------------
// If we have no damage zones allocated, we ignore any damage sent to us
//----------------------------------------------------------------------
//
Verify(message->damageZone < damageZoneCount);
Verify(message->damageZone >= 0 );
Check_Pointer(damageZones);
Check(damageZones[message->damageZone]);
damageZones[message->damageZone]->TakeDamage(message->damageData);
Check_Fpu();
}
//#############################################################################
// Construction and Destruction
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Entity::Entity(
Entity::MakeMessage *creation_message,
Entity::SharedData &virtual_data
):
Simulation(creation_message->classToCreate,virtual_data),
staticVideoSocket(NULL),
dynamicVideoSocket(NULL),
audioSocket(NULL)
{
Check_Pointer(this);
Check(creation_message);
//
// Get host manager pointer
//
HostManager *host_manager;
Check(application);
host_manager = application->GetHostManager();
Check(host_manager);
//
//-----------------------------
// Initialize the entity fields
//-----------------------------
//
playerLink = NULL;
resourceID = creation_message->resourceID;
owningPlayer = (Player*)host_manager->GetEntityPointer(
creation_message->owningPlayerID
);
simulationFlags = creation_message->instanceFlags;
entityID = creation_message->entityID;
ownerID = creation_message->ownerID;
creationTime = Now();
interestZoneID = creation_message->interestZoneID;
interestCount = 0;
// initialize attributes
localOrigin = creation_message->localOrigin;
updateOrigin = localOrigin;
localToWorld = localOrigin;
//
// Render interpolation starts with nothing to blend: the previous
// origin IS the current one and no fraction of a step is outstanding,
// so GetRenderToWorld hands back localToWorld until the first step has
// actually been taken.
//
renderPreviousOrigin = localOrigin;
renderStepFraction = (Scalar) 0;
// initialize camera stuff
cameraOffset = Origin::Identity;
SetInvalidFlag();
// initialize model stuff
lastPerformance = creationTime;
subsystemCount = 0;
subsystemArray = NULL;
damageZones = NULL;
damageZoneCount = 0;
//
//--------------------------------
// Allocate the damageZones array
//--------------------------------
//
//
// Read In DamageZones and Create DamageZoneID's
//
if (resourceID != ResourceDescription::NullResourceID)
{
ResourceDescription *dmg_res =
application->GetResourceFile()->SearchList(
resourceID,
ResourceDescription::DamageZoneStreamResourceType
);
//
//--------------------------------------------
// Allocate damageZones for this entity only
// if they were defined in the resource
//--------------------------------------------
//
if (dmg_res)
{
dmg_res->Lock();
DynamicMemoryStream damage_zone_stream(
dmg_res->resourceAddress,
dmg_res->resourceSize);
damage_zone_stream >> damageZoneCount;
damageZones = new (::DamageZone(*[damageZoneCount]));
Register_Pointer(damageZones);
dmg_res->Unlock();
}
}
//
//---------------------------------------------------------
// Create a new entity ID and assign ownership to this host
// If (this is a master entity) or
// (this is a dynamic hermit) or
// (this is the initial independent)
//---------------------------------------------------------
//
if (
(GetInstance() == MasterInstance) ||
(GetInstance() == HermitInstance && IsDynamic()) ||
(
(GetInstance() == IndependantInstance) &&
(creation_message->ownerID == NullHostID ||
creation_message->ownerID == MapHostID)
)
)
{
entityID = host_manager->MakeUniqueEntityID();
ownerID = host_manager->GetLocalHostID();
creation_message->entityID = entityID;
creation_message->ownerID = ownerID;
}
//
//--------------------------------------------
// Tell the host manager about this new entity
//--------------------------------------------
//
Check(host_manager);
host_manager->NotifyOfEntityCreation(this, creation_message);
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Entity*
Entity::Make(Entity::MakeMessage *creation_message)
{
return new Entity(creation_message,DefaultData);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
Entity::CreateMakeMessage(
MakeMessage *creation_message,
#if DEBUG_LEVEL>0
NotationFile *model_file,
#else
NotationFile *,
#endif
const ResourceDirectories *
)
{
Check(creation_message);
Check(model_file);
char *p;
creation_message->messageLength = sizeof(Entity::MakeMessage);
creation_message->messageID = Entity::MakeMessageID;
creation_message->owningPlayerID = EntityID::Null;
creation_message->messageFlags = 0;
creation_message->classToCreate = RegisteredClass::TrivialEntityClassID;
creation_message->instanceFlags = 0; //DefaultFlags;
if ((p = strtok(NULL, " ")) == NULL)
{
std::cerr << "X position coordinate missing!\n";
return False;
}
creation_message->localOrigin.linearPosition.x = atof(p);
if ((p = strtok(NULL, " ")) == NULL)
{
std::cerr << "Y position coordinate missing!\n";
return False;
}
creation_message->localOrigin.linearPosition.y = atof(p);
if ((p = strtok(NULL, " ")) == NULL)
{
std::cerr << "Z position coordinate missing!\n";
return False;
}
creation_message->localOrigin.linearPosition.z = atof(p);
EulerAngles pyr;
if ((p = strtok(NULL, " ")) == NULL)
{
std::cerr << "Pitch missing!\n";
return False;
}
pyr.pitch = atof(p);
if ((p = strtok(NULL, " ")) == NULL)
{
std::cerr << "Yaw missing!\n";
return False;
}
pyr.yaw = atof(p);
if ((p = strtok(NULL, " ")) == NULL)
{
std::cerr << "Roll missing!\n";
return False;
}
pyr.roll = atof(p);
creation_message->localOrigin.angularPosition = pyr;
return True;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
Entity::CreateMakeMapMessage(
MakeMapMessage *creation_message,
ResourceDescription::ResourceID resource_id,
#if DEBUG_LEVEL>0
NotationFile *model_file,
#else
NotationFile *,
#endif
const ResourceDirectories *
)
{
Check(creation_message);
Check(model_file);
char *p;
creation_message->messageLength = sizeof(Entity::MakeMapMessage);
creation_message->messageID = Entity::MakeMessageID;
creation_message->messageFlags = Entity::MakeMessage::MapStreamMarkerFlag;
if ((p = strtok(NULL, " ")) == NULL)
{
std::cerr << "X position coordinate missing!\n";
return False;
}
creation_message->localOrigin.linearPosition.x = atof(p);
if ((p = strtok(NULL, " ")) == NULL)
{
std::cerr << "Y position coordinate missing!\n";
return False;
}
creation_message->localOrigin.linearPosition.y = atof(p);
if ((p = strtok(NULL, " ")) == NULL)
{
std::cerr << "Z position coordinate missing!\n";
return False;
}
creation_message->localOrigin.linearPosition.z = atof(p);
EulerAngles pyr = EulerAngles::Identity;
#if 0
if ((p = strtok(NULL, " ")) == NULL)
{
std::cerr << "Pitch missing!\n";
return False;
}
pyr.pitch = atof(p);
if ((p = strtok(NULL, " ")) == NULL)
{
std::cerr << "Yaw missing!\n";
return False;
}
pyr.yaw = atof(p);
if ((p = strtok(NULL, " ")) == NULL)
{
std::cerr << "Roll missing!\n";
return False;
}
pyr.roll = atof(p);
#endif
creation_message->localOrigin.angularPosition = pyr;
creation_message->resourceID = resource_id;
return True;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Entity::DestroyEntityMessageHandler(Message *)
{
CondemnToDeathRow();
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Entity::~Entity()
{
//
// Notify the host manager that we are going away
//
Check(application);
application->GetHostManager()->NotifyOfEntityDestruction(this);
//
// Delete the subsystem array
//
if (subsystemArray)
{
Check_Pointer(subsystemArray);
for (int i=0; i<subsystemCount; ++i)
{
if (subsystemArray[i])
{
Unregister_Object(subsystemArray[i]);
delete subsystemArray[i];
}
}
Unregister_Pointer(subsystemArray);
delete subsystemArray;
}
if (damageZones)
{
Check_Pointer(damageZones);
for (int i=0; i<damageZoneCount; ++i)
{
if (damageZones[i])
{
Unregister_Object(damageZones[i]);
delete damageZones[i];
}
}
Unregister_Pointer(damageZones);
delete damageZones;
}
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Entity::CondemnToDeathRow()
{
Check(this);
Check(application);
EntityManager *entity_manager = application->GetEntityManager();
Check(entity_manager);
//
//-------------------------------------------------
// Make sure we are not entered into deathrow twice
//-------------------------------------------------
//
if (!IsCondemned())
{
entity_manager->deathRow->Add(this);
SetCondemnedFlag();
NeverExecute();
}
Check_Fpu();
}
//#############################################################################
// Test Support
//
Logical
Entity::TestInstance() const
{
return IsDerivedFrom(*GetClassDerivations());
}
//##########################################################################
// Render interpolation - see entity.h for why, and for why it is render
// only. RP412INTERP=0 turns it off so the stepping it removes can be seen
// again on a test machine without a rebuild.
//##########################################################################
//
static Logical
RenderInterpolationEnabled()
{
static int enabled = -1;
if (enabled < 0)
{
const char *setting = getenv("RP412INTERP");
enabled = (setting != NULL && atoi(setting) == 0) ? 0 : 1;
if (!enabled)
{
DEBUG_STREAM << "Render: interpolation off (RP412INTERP=0) - "
<< "drawn motion steps at the physics rate\n" << std::flush;
}
}
return enabled ? True : False;
}
void
Entity::SnapshotRenderOrigin()
{
Check(this);
renderPreviousOrigin = localOrigin;
}
void
Entity::SetRenderStepFraction(Scalar fraction)
{
Check(this);
renderStepFraction = fraction;
}
void
Entity::GetRenderToWorld(LinearMatrix *out)
{
Check(this);
Check_Pointer(out);
//
// Nothing to blend towards: no fraction of a step outstanding, no
// fixed step at all, or interpolation switched off. Hand back exactly
// what every caller used before this existed.
//
if (renderStepFraction <= (Scalar) 0 || !RenderInterpolationEnabled())
{
*out = localToWorld;
return;
}
//
// Origin::Lerp does the position and the shortest-arc quaternion, and
// normalises - which over a 20 ms step is indistinguishable from a
// true slerp and a good deal cheaper.
//
Origin blended;
blended.Lerp(renderPreviousOrigin, localOrigin, renderStepFraction);
*out = blended;
Check_Fpu();
}
//##########################################################################
// Renderer Support
//
void
Entity::AddStaticVideoComponent(Component *component)
{
Check(component);
Check(&staticVideoSocket);
staticVideoSocket.Add(component);
Check_Fpu();
}
void
Entity::AddDynamicVideoComponent(Component *component)
{
Check(component);
Check(&dynamicVideoSocket);
dynamicVideoSocket.Add(component);
Check_Fpu();
}
void
Entity::AddAudioComponent(Component *component)
{
Check(component);
Check(&audioSocket);
audioSocket.Add(component);
Check_Fpu();
}
Enumeration
Entity::GetAudioRepresentation(Entity *linked_entity)
{
Check(this);
Check(linked_entity);
return
(linked_entity == this) ?
InternalAudioRepresentation : ExternalAudioRepresentation;
}
//#############################################################################
// Interest Support
//
void
Entity::BecomeInteresting()
{
BecomeInterestingMessage
message(
Entity::BecomeInterestingMessageID,
sizeof(BecomeInterestingMessage)
);
Receiver::Dispatch(&message);
Check_Fpu();
}
void Entity::BecomeInterestingMessageHandler(const Receiver::Message*)
{
++interestCount;
Check_Fpu();
}
void
Entity::BecomeUninterestingMessageHandler()
{
--interestCount;
Check_Fpu();
}
Enumeration
Entity::GetInterestPriority(Entity *linked_entity)
{
Check(this);
Check(linked_entity);
return (linked_entity == this) ? HighInterestPriority : LowInterestPriority;
}
int
Entity::GetDamageZoneIndex(const CString &damage_zone_name) const
{
for(int ii=0;ii<damageZoneCount;++ii)
{
if(damageZones[ii]->damageZoneName.Compare(damage_zone_name) == 0)
{
return ii;
}
}
return -1;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
Entity::CreateDamageZoneStream(
ResourceFile *resource_file,
const char *model_name,
NotationFile *model_file,
const ResourceDirectories *directories
)
{
Check(resource_file);
Check_Pointer(model_name);
Check(model_file);
Check_Pointer(directories);
//
//-------------------
// Find the .dmg file
//-------------------
//
const char* dmg_file_cname;
if (
!model_file->GetEntry(
"gamedata",
"DamageZones",
&dmg_file_cname
)
)
{
std::cerr << model_name << " is missing .dmg file specification!\n";
return -1;
}
CString dmg_filename =
MakePathedFilename(directories->modelDirectory, dmg_file_cname);
NotationFile *dmg_file = new NotationFile(dmg_filename);
Register_Object(dmg_file);
int page_count = dmg_file->PageCount();
if (!page_count)
{
std::cerr << dmg_filename << " is empty or missing!\n";
Unregister_Object(dmg_file);
delete dmg_file;
return -1;
}
DynamicMemoryStream damage_stream;
damage_stream << page_count;
NameList *dmg_namelist = dmg_file->MakePageList();
Register_Object(dmg_namelist);
NameList::Entry *dmg_entry = dmg_namelist->GetFirstEntry();
CString dmg_page_name;
while(dmg_entry)
{
dmg_page_name = dmg_entry->GetName();
DamageZone::CreateStreamedDamageZone(
model_file,
model_name,
NULL,
dmg_page_name,
&damage_stream,
dmg_file,
directories
);
dmg_entry = dmg_entry->GetNextEntry();
}
//
//~~~~~~~~~~~~~~~~~~~~~~~
// Add Stream to Resource
//~~~~~~~~~~~~~~~~~~~~~~~
//
ResourceDescription *new_res =
resource_file->AddResourceMemoryStream(
model_name,
ResourceDescription::DamageZoneStreamResourceType,
1,
ResourceDescription::Preload,
&damage_stream
);
Check(new_res);
Unregister_Object(dmg_file);
delete dmg_file;
Unregister_Object(dmg_namelist);
delete dmg_namelist;
return new_res->resourceID;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ResourceDescription::ResourceID
Entity::CreateExplosionTableStream(
ResourceFile *resource_file,
const char *model_name,
NotationFile *model_file,
const ResourceDirectories *directories
)
{
Check(resource_file);
Check_Pointer(model_name);
Check(model_file);
Check_Pointer(directories);
//
//~~~~~~~~~~~~~~~~~
// Find *.dmg file
//~~~~~~~~~~~~~~~~~
//
const char *entry_data;
if (!model_file->GetEntry(
"gamedata",
"DamageZones",
&entry_data
)
)
{
DEBUG_STREAM << model_name << " is missing .dmg file specification!\n" << std::flush;
return -1;
}
CString filename =
MakePathedFilename(directories->modelDirectory, entry_data);
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Make a notation file of the DMG Entries
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
NotationFile *dmg_file = new NotationFile(filename);
Register_Object(dmg_file);
if(dmg_file->IsEmpty())
{
DEBUG_STREAM << model_name << " *.dmg file is empty or missing!\n" << std::flush;
Unregister_Object(dmg_file);
delete dmg_file;
return -1;
}
DynamicMemoryStream explosion_stream;
NameList *dmg_namelist = dmg_file->MakePageList();
Register_Object(dmg_namelist);
NameList::Entry *dmg_entry = dmg_namelist->GetFirstEntry();
while(dmg_entry)
{
const char *entry_data;
if (dmg_file->GetEntry(
dmg_entry->GetName(),
"ExplosionTable",
&entry_data
)
)
{
CString filename =
MakePathedFilename(directories->modelDirectory, entry_data);
NotationFile *exp_file = new NotationFile(filename);
Register_Object(exp_file);
if(exp_file->IsEmpty())
{
std::cerr<<model_name<<" : "<<filename<<" is emoty or missing! \n";
Unregister_Object(dmg_file);
delete dmg_file;
Unregister_Object(dmg_namelist);
delete dmg_namelist;
Unregister_Object(exp_file);
delete exp_file;
return ResourceDescription::NullResourceID;
}
else
{
ExplosionTable::CreateStreamedExplosionTable(
resource_file,
model_name,
exp_file,
filename,
&explosion_stream
);
}
Unregister_Object(exp_file);
delete exp_file;
}
else
{
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Write Out no Explosions for this DamageZone
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int zero_int(0);
explosion_stream << zero_int;
}
dmg_entry = dmg_entry->GetNextEntry();
}
ResourceDescription *new_res =
resource_file->AddResourceMemoryStream(
model_name,
ResourceDescription::ExplosionTableStreamResourceType,
1,
ResourceDescription::Preload,
&explosion_stream
);
Check(new_res);
Unregister_Object(dmg_file);
delete dmg_file;
Unregister_Object(dmg_namelist);
delete dmg_namelist;
return new_res->resourceID;
}
#if defined(TEST_CLASS)
# include "entity.tcp"
#endif