Files
RP412/MUNGA/SIMULATE.cpp
T
CydandClaude Fable 5 34abf40e7f Fifty hertz is the physics
RP412PHYSICSHZ defaults to 50: the simulation advances in fixed 20 ms
steps whatever the display does, and every machine plays the same race.
The proof preceded the promotion - a scripted lap with a crash, a burn,
a tumble and two respawns runs bit-identical at 30, 60 and 144 fps, and
identical runs reproduce exactly, neither of which was ever true of
this engine at any frame rate.

Fifty because it is exact on the engine's millisecond clock (a rate
like 60 quietly becomes 17 ms steps wearing the wrong name), and
because its settled hover ride height measured closest to the
frame-coupled physics the game has always run - the least change of
feel for the most change of correctness. The pods' 25 and the smoother
100 stay one line away for the play testers, and 0 keeps the original
frame-coupled behaviour for comparison, where the frame rate is part of
the simulation.

Carried-over environ files do not mention the option, so existing
testers get 50 on their next build and rpl4.log names both the option
they have not heard of and the mode every launch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-09 22:05:45 -05:00

1084 lines
26 KiB
C++

#include "munga.h"
#pragma hdrstop
#include "simulate.h"
#include "update.h"
#include "app.h"
#if defined(TRACE_EXECUTE_WATCHERS)
static BitTrace Execute_Watchers("Execute Watchers");
#define SET_EXECUTE_WATCHERS() Execute_Watchers.Set()
#define CLEAR_EXECUTE_WATCHERS() Execute_Watchers.Clear()
#else
#define SET_EXECUTE_WATCHERS()
#define CLEAR_EXECUTE_WATCHERS()
#endif
//#############################################################################
//######################## StateIndicator ###############################
//#############################################################################
//#############################################################################
// Construction and Destruction
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
StateIndicator::StateIndicator():
audioWatcherSocket(NULL),
videoWatcherSocket(NULL),
gaugeWatcherSocket(NULL)
{
Check_Pointer(this);
stateCount = 0;
oldState = 0;
currentState = 0;
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
StateIndicator::StateIndicator(unsigned max_states):
audioWatcherSocket(NULL),
videoWatcherSocket(NULL),
gaugeWatcherSocket(NULL)
{
Check_Pointer(this);
stateCount = max_states;
oldState = max_states;
currentState = max_states;
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
StateIndicator::StateIndicator(const StateIndicator &state_indicator):
audioWatcherSocket(NULL),
videoWatcherSocket(NULL),
gaugeWatcherSocket(NULL)
{
Check_Pointer(this);
// Do not perform deep copy of watchers
stateCount = state_indicator.stateCount;
oldState = state_indicator.oldState;
currentState = state_indicator.currentState;
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
StateIndicator::~StateIndicator()
{
Check(this);
//
// Manual deletion of existing watchers
//
{
SChainIteratorOf<Component*> iterator(&audioWatcherSocket);
#if DEBUG_LEVEL>2
Component *component;
while ((component = iterator.ReadAndNext()) != NULL)
{
Check(component);
Dump(component->GetClassID());
}
Warn(iterator.GetSize() != 0);
#endif
iterator.DeletePlugs();
}
{
SChainIteratorOf<Component*> iterator(&videoWatcherSocket);
#if DEBUG_LEVEL>2
Component *component;
while ((component = iterator.ReadAndNext()) != NULL)
{
Check(component);
Dump(component->GetClassID());
}
Warn(iterator.GetSize() != 0);
#endif
iterator.DeletePlugs();
}
{
SChainIteratorOf<Component*> iterator(&gaugeWatcherSocket);
#if DEBUG_LEVEL>2
Component *component;
while ((component = iterator.ReadAndNext()) != NULL)
{
Check(component);
Dump(component->GetClassID());
}
Warn(iterator.GetSize() != 0);
#endif
iterator.DeletePlugs();
}
Check_Fpu();
}
//#############################################################################
// State stuff
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
StateIndicator&
StateIndicator::operator=(const StateIndicator &state_indicator)
{
// Do not perform assignment of watchers
stateCount = state_indicator.stateCount;
oldState = state_indicator.oldState;
currentState = state_indicator.currentState;
Check_Fpu();
return *this;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
StateIndicator::operator==(const StateIndicator &state_indicator) const
{
Check_Fpu();
return
(
stateCount == state_indicator.stateCount &&
oldState == state_indicator.oldState &&
currentState == state_indicator.currentState
);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
StateIndicator::SetState(unsigned new_state)
{
Check(this);
Verify(new_state < stateCount);
//
//--------------------------------------------------------------------------
// See if the state really changes
//
// NOTE - the old state does change to the current state, simulating a loop
// in the state engine. If it turns out that someone is watching the
// level of the state indicator and doing their own edge detection,
// this might possibly maybe screw something up
//--------------------------------------------------------------------------
//
oldState = currentState;
if (new_state == currentState)
{
return;
}
//
//-------------------------------------------------------------------
// If the state has changed, update the state values and then run any
// watchers
//-------------------------------------------------------------------
//
currentState = new_state;
Component *watcher;
SET_EXECUTE_WATCHERS();
// Audio
{
SChainIteratorOf<Component*> iterator(audioWatcherSocket);
Check(&iterator);
while ((watcher = iterator.ReadAndNext()) != NULL)
{
watcher->Execute();
}
}
// Video
{
SChainIteratorOf<Component*> iterator(videoWatcherSocket);
Check(&iterator);
while ((watcher = iterator.ReadAndNext()) != NULL)
{
watcher->Execute();
}
}
// Gauge
{
SChainIteratorOf<Component*> iterator(gaugeWatcherSocket);
Check(&iterator);
while ((watcher = iterator.ReadAndNext()) != NULL)
{
watcher->Execute();
}
}
CLEAR_EXECUTE_WATCHERS();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
std::ostream& operator << (std::ostream &strm, const StateIndicator &state_indicator)
{
Check(&state_indicator);
strm << "[" << state_indicator.stateCount << ",";
strm << state_indicator.oldState << ",";
strm << state_indicator.currentState << "]";
return strm;
}
//#############################################################################
// Test Support
//
Logical
StateIndicator::TestInstance() const
{
return True;
}
//#############################################################################
//########################## Simulation #################################
//#############################################################################
//#############################################################################
// Virtual Data support
//
Derivation* Simulation::GetClassDerivations()
{
static Derivation classDerivations(Receiver::GetClassDerivations(), "Simulation");
return &classDerivations;
}
Simulation::SharedData
Simulation::DefaultData(
Simulation::GetClassDerivations(),
Simulation::GetMessageHandlers(),
Simulation::GetAttributeIndex(),
Simulation::StateCount
);
//#############################################################################
// Model support
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
//##########################################################################
// Net clock - see SIMULATE.h for why the sender's timestamp is estimated
// rather than used as it stands.
//##########################################################################
namespace
{
enum
{
netClockMaxPeers = 16,
// Samples per rolling minimum. A peer sends one record per
// simulation per frame, so at eight vehicles and 60 fps this is
// well under a second - fast enough to follow a route change,
// long enough that the minimum means something.
netClockWindow = 128,
// The furthest back we will believe a timestamp. Beyond this the
// packet is stale or the estimate is wrong, and extrapolating a
// vehicle half a second forward does more harm than the lag we
// are correcting.
netClockMaxLagTicks = 500
};
struct PeerClock
{
HostID host;
Logical inUse;
Logical settled;
long offsetTicks; // our clock - their clock
long windowMinTicks;
int windowCount;
};
PeerClock gPeerClocks[netClockMaxPeers];
HostID gUpdateSender = 0;
Logical gUpdateSenderValid = False;
Logical NetClockEnabled()
{
static int enabled = -1;
if (enabled < 0)
{
const char *setting = getenv("RP412NETCLOCK");
enabled = (setting != NULL && atoi(setting) == 0) ? 0 : 1;
if (!enabled)
{
DEBUG_STREAM << "NetClock: disabled by RP412NETCLOCK=0 - "
<< "replicants dead-reckon from arrival time\n" << std::flush;
}
}
return enabled ? True : False;
}
PeerClock *FindPeer(HostID host)
{
PeerClock *free_slot = NULL;
for (int i = 0; i < netClockMaxPeers; ++i)
{
if (gPeerClocks[i].inUse)
{
if (gPeerClocks[i].host == host)
{
return &gPeerClocks[i];
}
}
else if (free_slot == NULL)
{
free_slot = &gPeerClocks[i];
}
}
if (free_slot != NULL)
{
free_slot->inUse = True;
free_slot->host = host;
free_slot->settled = False;
free_slot->offsetTicks = 0;
free_slot->windowMinTicks = 0;
free_slot->windowCount = 0;
}
return free_slot;
}
}
void NetClock_BeginUpdate(HostID sender)
{
gUpdateSender = sender;
gUpdateSenderValid = True;
}
void NetClock_EndUpdate()
{
gUpdateSenderValid = False;
}
void NetClock_Reset()
{
memset(gPeerClocks, 0, sizeof(gPeerClocks));
gUpdateSenderValid = False;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Simulation::ReadUpdateRecord(UpdateRecord *message)
{
Check(this);
Check_Pointer(message);
//
//------------------------------------------------------------------
// When this update arrived is not when it was taken. Put lastUpdate
// at the sender's sampling moment, expressed in our clock, so the
// dead reckoner extrapolates over the network latency instead of
// starting from scratch once it has already elapsed.
//------------------------------------------------------------------
//
long now_ticks = Now().ticks;
long local_ticks = now_ticks;
PeerClock *peer = gUpdateSenderValid && NetClockEnabled()
? FindPeer(gUpdateSender) : NULL;
if (peer != NULL)
{
//
// sample = trueOffset + oneWayLatency, so the running minimum
// converges on the offset from above.
//
long sample = now_ticks - message->timeStamp.ticks;
if (!peer->settled)
{
peer->settled = True;
peer->offsetTicks = sample;
peer->windowMinTicks = sample;
peer->windowCount = 0;
DEBUG_STREAM << "NetClock: host " << peer->host
<< " first sample, offset " << sample << " ms\n" << std::flush;
}
else
{
if (sample < peer->windowMinTicks)
{
peer->windowMinTicks = sample;
}
if (sample < peer->offsetTicks)
{
peer->offsetTicks = sample; // a shorter path: believe it now
}
if (++peer->windowCount >= netClockWindow)
{
//
// Close the window: adopt its minimum even if it is
// LARGER than the running estimate, which is how the
// figure follows clock drift and a route that got
// slower rather than staying pinned to one old packet.
//
long moved = peer->windowMinTicks - peer->offsetTicks;
if (moved > 50 || moved < -50)
{
DEBUG_STREAM << "NetClock: host " << peer->host
<< " offset " << peer->offsetTicks << " -> "
<< peer->windowMinTicks << " ms\n" << std::flush;
}
peer->offsetTicks = peer->windowMinTicks;
peer->windowMinTicks = sample;
peer->windowCount = 0;
}
}
local_ticks = message->timeStamp.ticks + peer->offsetTicks;
//
// Never ahead of our own clock, and never further back than we
// are willing to extrapolate.
//
if (local_ticks > now_ticks)
{
local_ticks = now_ticks;
}
else if (now_ticks - local_ticks > netClockMaxLagTicks)
{
local_ticks = now_ticks - netClockMaxLagTicks;
}
}
lastUpdate.ticks = local_ticks;
SetSimulationState(message->simulationState);
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Simulation::WriteUpdateRecord(
UpdateRecord *message,
int update_model
)
{
Check(this);
Check_Pointer(message);
message->timeStamp = lastPerformance;
message->simulationState = GetSimulationState();
message->recordLength = sizeof(*message);
message->recordID = (Word)update_model;
lastUpdate = lastPerformance;
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Simulation::WriteSimulationUpdate(MemoryStream *update_stream)
{
Check(this);
Check(update_stream);
//
//-----------------
// Write the update
//-----------------
//
int bit=0;
int update_model = updateModel;
updateModel = 0;
while (update_model)
{
if (update_model & 1)
{
UpdateRecord* update = (UpdateRecord*)update_stream->GetPointer();
WriteUpdateRecord(update, bit);
update_stream->AdvancePointer(update->recordLength);
}
update_model >>= 1;
++bit;
}
Check_Fpu();
}
//#############################################################################
// Construction and Destruction
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Simulation::Simulation(
Simulation::ClassID class_ID,
Simulation::SharedData &virtual_data
):
Receiver(class_ID, virtual_data),
simulationState(GetSharedData()->stateCount),
audioWatcherSocket(NULL),
videoWatcherSocket(NULL),
gaugeWatcherSocket(NULL),
effectWatcherSocket(NULL)
{
Check_Pointer(this);
SetSimulationState(DefaultState);
lastPerformance = Now();
lastUpdate = lastPerformance;
activePerformance = &Simulation::DoNothingOnce;
updateModel = 0;
simulationFlags = 0;
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Simulation::~Simulation()
{
Check(this);
//
// Watchers should be deleted by renderers by now
//
#if DEBUG_LEVEL>0
{
SChainIteratorOf<Component*> iterator(&audioWatcherSocket);
Verify(iterator.GetSize() == 0);
}
{
SChainIteratorOf<Component*> iterator(&videoWatcherSocket);
Verify(iterator.GetSize() == 0);
}
{
SChainIteratorOf<Component*> iterator(&gaugeWatcherSocket);
Verify(iterator.GetSize() == 0);
}
#endif
Check_Fpu();
}
//#############################################################################
// Attribute Support
//
const Simulation::AttributePointer
Simulation::NullAttribute = NULL;
const Simulation::IndexEntry
Simulation::AttributePointers[]=
{
{
Simulation::SimulationStateAttributeID,
"SimulationState",
(Simulation::AttributePointer)&Simulation::simulationState
}
};
Simulation::AttributeIndexSet& Simulation::GetAttributeIndex()
{
static Simulation::AttributeIndexSet attributeIndex(ELEMENTS(Simulation::AttributePointers),
Simulation::AttributePointers
);
return attributeIndex;
}
void*
Simulation::GetAttributePointer(Simulation::AttributeID attribute)
{
Check(this);
AttributePointer attr =
GetSharedData()->activeAttributeIndex->Find(attribute);
Check_Fpu();
if (attr == NullAttribute)
{
return NULL;
}
else
{
return &(this->*attr);
}
}
void*
Simulation::GetAttributePointer(const char* attribute_name)
{
Check(this);
AttributePointer attr =
GetSharedData()->activeAttributeIndex->Find(attribute_name);
Check_Fpu();
if (attr == NullAttribute)
{
return NULL;
}
else
{
return &(this->*attr);
}
}
//#############################################################################
// RP412PHYSICSHZ - the size of one simulation step, as a rate in hertz.
//
// The engine simulates TO a timestamp: every entity keeps a lastPerformance
// marking how far it has been simulated, and PerformAndWatch hands Perform()
// the difference. That difference used to be however long the last frame
// took, which made the frame rate part of the physics - explicitly so, since
// Mover scales its bounce and penetration thresholds by delta_t.
//
// Advancing lastPerformance in fixed steps instead makes it the accumulator
// a fixed-step loop needs, and every Perform() in the game gets an identical
// dt without one of them being touched.
//
// 0 restores the old behaviour for comparison. The RATE is a game-feel
// decision, not a technical one: thirty years of handling constants were
// tuned against the DOS build's 40 ms steps, and RP412 has been running
// ~18 ms variable ones, so the feel has already drifted. Whatever is chosen
// here becomes the canonical physics for pods and PCs alike.
//#############################################################################
static Scalar
FixedPhysicsStep()
{
static Scalar
step = (Scalar) -1;
if (step < (Scalar) 0)
{
const char
*setting = getenv("RP412PHYSICSHZ");
//
// 50 Hz is the default: a 20 ms step, exact on the millisecond
// clock, and the rate whose settled hover ride height measured
// closest to the frame-coupled physics the game has always run.
// Proven before it was defaulted - a scripted lap with a crash,
// a burn and two respawns runs bit-identical at 30, 60 and 144
// fps, and identical runs reproduce exactly. 0 restores the
// original frame-coupled behaviour, where the frame rate is
// part of the physics.
//
int rate = (setting != NULL) ? atoi(setting) : 50;
//
// Guard the arithmetic rather than the taste: a rate below the
// frame rate is a legitimate choice (the pods ran at 25), but a
// step of zero or a negative one is not a choice at all.
//
if (rate < 0)
{
rate = 0;
}
if (rate > 1000)
{
rate = 1000;
}
step = (rate > 0) ? ((Scalar) 1 / (Scalar) rate) : (Scalar) 0;
DEBUG_STREAM << "Physics: ";
if (rate > 0)
{
DEBUG_STREAM << "fixed step, " << rate << " Hz";
//
// The engine's clock counts MILLISECONDS, so a step is
// really round(1000/rate) ms. A rate that does not divide
// 1000 evenly therefore runs at a neighbouring rate wearing
// this one's name - 60 asks for 16.67 ms and gets 17, which
// is 58.8 Hz. Say so, and name the rates that mean what
// they say.
//
if ((1000 % rate) != 0)
{
int step_ms = (1000 + rate / 2) / rate;
DEBUG_STREAM << " - NOT millisecond-exact, steps will run "
<< step_ms << " ms (" << (1000.0f / (float) step_ms)
<< " Hz). 25, 50 and 100 are exact";
}
}
else
{
DEBUG_STREAM << "frame-coupled (RP412PHYSICSHZ=0)";
}
DEBUG_STREAM << "\n" << std::flush;
}
return step;
}
//
// How far behind one frame may catch up: a quarter second of simulation,
// whatever the rate - enough to ride out a texture load or an alt-tab,
// short of letting a stalled machine spiral. Counted in steps because the
// loop is, so 6 steps at 25 Hz, 12 at 50, 25 at 100.
//
static int
MaximumCatchUpSteps(Scalar step)
{
int steps = (int)((Scalar) 0.25 / step);
return (steps < 4) ? 4 : steps;
}
// how many fixed steps the whole simulation has taken - the trace prints it,
// so 'is the step actually fixed' is answered by measurement not by reading
long gPhysicsStepsTaken = 0;
//#############################################################################
// Simulation Support
//
Scalar
Simulation::FixedStep()
{
return FixedPhysicsStep();
}
void
Simulation::PerformTo(const Time& till)
{
Check(this);
Check(&till);
Scalar step = FixedPhysicsStep();
if (step > (Scalar) 0)
{
//
//------------------------------------------------------------------
// Fixed step. The simulation advances in whole steps of the same
// size on every machine, and whatever is left over waits for the
// next frame - lastPerformance is the accumulator, and always was.
//
// Before this, the slice was simply however long the last frame
// took, so a 30 fps machine integrated gravity in 33 ms steps and
// a 144 fps machine in 7 ms ones. Nothing in any Perform()
// changes: it is handed a dt it can rely on instead of one that
// depended on the graphics card.
//
// NOTE the caller decides the interleaving. An entity's spring
// forces are computed from its subsystems (the VTV reads its
// thrusters' measured heights), so the subsystems and the entity
// must advance TOGETHER, one step at a time -
// Entity::PerformAndWatch owns that loop and hands everyone the
// same sub-frame 'till'. Stepping a subsystem all the way to the
// frame boundary before its owner moves at all is how the first
// attempt at this produced a pod that climbed at 30 fps and flew
// level at 144: two spring impulses from one stale height sample.
//------------------------------------------------------------------
//
Scalar behind = till - lastPerformance;
int taken = 0;
int max_steps = MaximumCatchUpSteps(step);
while (behind >= step && taken < max_steps)
{
Perform(step);
++gPhysicsStepsTaken;
lastPerformance += step;
behind -= step;
++taken;
}
//
// A machine that cannot keep up must not try to buy back the whole
// backlog next frame - that costs more time, which makes a bigger
// backlog. Drop what could not be run and carry on: the game slows
// down rather than seizing, and it does so identically everywhere.
//
if (taken >= max_steps && behind >= step)
{
lastPerformance = till;
}
}
else
{
Scalar slice = till - lastPerformance;
lastPerformance = till;
Perform(slice);
}
Check_Fpu();
}
void
Simulation::BeginStep()
{
// nothing by default - see the header
}
void
Simulation::WatchAndWrite(MemoryStream *update_stream)
{
Check(this);
if (!AreWatchersDelayed())
{
ExecuteWatchers();
}
WriteSimulationUpdate(update_stream);
Check_Fpu();
}
void
Simulation::PerformAndWatch(
const Time& till,
MemoryStream *update_stream
)
{
Check(this);
Check(&till);
PerformTo(till);
WatchAndWrite(update_stream);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Simulation::DoNothingOnce(Scalar)
{
NeverExecute();
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Simulation::DoNothing(Scalar)
{
Check_Fpu();
}
//#############################################################################
// Watcher Support
//
void
Simulation::ExecuteWatchers()
{
SET_EXECUTE_WATCHERS();
Component *watcher;
// Audio
{
SChainIteratorOf<Component*> iterator(audioWatcherSocket);
while ((watcher = iterator.ReadAndNext()) != NULL)
{
watcher->Execute();
}
}
// Video
{
SChainIteratorOf<Component*> iterator(videoWatcherSocket);
while ((watcher = iterator.ReadAndNext()) != NULL)
{
watcher->Execute();
}
}
// Gauge
{
SChainIteratorOf<Component*> iterator(gaugeWatcherSocket);
while ((watcher = iterator.ReadAndNext()) != NULL)
{
watcher->Execute();
}
}
// Effect
{
SChainIteratorOf<Component*> iterator(effectWatcherSocket);
while ((watcher = iterator.ReadAndNext()) != NULL)
{
watcher->Execute();
}
}
CLEAR_EXECUTE_WATCHERS();
}
//#############################################################################
// Test Support
//
Logical
Simulation::TestInstance() const
{
return IsDerivedFrom(*GetClassDerivations());
}
//#############################################################################
//################### Simulation::AttributeIndexSet #####################
//#############################################################################
const Simulation::AttributeIndexSet
Simulation::AttributeIndexSet::NullSet;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Simulation__AttributeIndexSet::~Simulation__AttributeIndexSet()
{
if (attributeIndex)
{
Unregister_Pointer(attributeIndex);
delete[] attributeIndex;
}
};
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Simulation::AttributeIndexSet::Build(
Simulation::AttributeID count,
const Simulation::IndexEntry index_table[],
const Simulation::AttributeIndexSet *inheritance
)
{
//
//-------------------------------------------------------
// Find out the highest message type we have to deal with
//-------------------------------------------------------
//
Check(this);
Check_Pointer(index_table);
entryCount = 0;
Simulation::AttributeID i;
for (i=0; i<count; ++i)
{
if (index_table[i].entryID > entryCount)
{
entryCount = index_table[i].entryID;
}
}
if (inheritance)
{
Check(inheritance);
if (entryCount<inheritance->entryCount)
{
entryCount = inheritance->entryCount;
}
#if DEBUG_LEVEL>0
else if (entryCount > inheritance->entryCount)
{
i = inheritance->entryCount+1;
goto Check_Table;
}
#endif
}
else
{
Verify(entryCount == count);
#if DEBUG_LEVEL>0
i = 1;
Check_Table:
while (i <= entryCount)
{
int j;
for (j=0; j<count; ++j)
{
if (index_table[j].entryID == i)
{
break;
}
}
if (j == count)
{
break;
}
++i;
}
Verify(i > count);
#endif
}
//
//-----------------------------------------------------------------------
// Allocate the memory for the new handler set, and copy the inherited
// handlers to the new table. We are guaranteed to have enough space for
// the inherited table
//-----------------------------------------------------------------------
//
attributeIndex = new Simulation::IndexEntry[entryCount];
Check_Pointer(attributeIndex);
Register_Pointer(attributeIndex);
i = 0;
if (inheritance)
{
for (; i<inheritance->entryCount; ++i)
{
attributeIndex[i] = inheritance->attributeIndex[i];
}
}
//
//----------------------------------------------------------------------
// Step through the new table supplied, placing each handler in the slot
// determined by the message type
//----------------------------------------------------------------------
//
for (i=0; i<count; ++i)
{
Verify(!inheritance || index_table[i].entryID > inheritance->entryCount);
attributeIndex[index_table[i].entryID-1] = index_table[i];
}
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Simulation::AttributePointer
Simulation::AttributeIndexSet::Find(const char* attribute_name) const
{
Check(this);
Check_Pointer(attribute_name);
for (int attribute=0; attribute<entryCount; ++attribute)
{
if (!strcmp(attribute_name, attributeIndex[attribute].entryName))
{
Check_Fpu();
return attributeIndex[attribute].entryAddress;
}
}
Check_Fpu();
return Simulation::NullAttribute;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
const Simulation::IndexEntry*
Simulation::AttributeIndexSet::FindEntry(const char* attribute_name) const
{
Check(this);
Check_Pointer(attribute_name);
for (int attribute=0; attribute<entryCount; ++attribute)
{
if (!strcmp(attribute_name, attributeIndex[attribute].entryName))
{
Check_Fpu();
return &attributeIndex[attribute];
}
}
Check_Fpu();
return NULL;
}
void
Simulation::RequestEncore(Encore encore)
{
Check(this);
SetWatcherDelay();
Check(application);
UpdateManager *updater = application->GetUpdateManager();
Check(updater);
updater->RequestEncore(this, encore);
}
#if defined(TEST_CLASS) && 0
#include "model.tcp"
#endif