9.3. ReckoningStrategy Customization

The dead reckoning algorithm (ReckoningStrategy) used in the PiaReckoning module can be customized for each application.

Simple3dReckoningStrategy, which is provided as a library, is essentially an estimating algorithm. In an actual game program, the estimated values provided by Simple3dReckoningStrategy would need further correction to match a restriction on transitioning between game stages.

The source code for Simple3dReckoningStrategy is published in sources\reckoning\reckoning_Simple3dReckoningStrategy.cpp. Customize it as needed to match your game's design.

Reference:

In Simple3dReckoningStrategy, interpolation is performed using linear functions. Quadratic functions also can be used, but smooth interpolation becomes impossible when there are sudden (fast) movements because the error is so large. We recommend using linear functions for your normal action games.

 

Defining a Dead Reckoning Algorithm

The class that you specify for the ReckoningStrategy template parameter in ReckoningCloneElement must contain the following definitions.

Type

You can use typedef to define other types for classes in your ReckoningStrategy as long as they can be accessed with the type names you define.

 

Value

The type for values accessed using the SetValue() and GetValue() functions. Equivalent to specifying the Value template parameter from UnreliableCloneElement or ReliableCloneElement.

 

Sample

The type for sample values. To save, as is, a value accessed with the SetValue() or GetValue() function as the sample value, specify the same type you used for Value. This type is used for the data that is actually sent. Data serialized with SerializePolicy uses the Sample type rather than the Value type. Keep this in mind when you specify SerializePolicy.

 

SetValueArg

The type for data passed to the ReckoningStrategy::CheckSample() and ReckoningStrategy::MakeSample() functions when the SetValue() function is called. Use this type when you have data other than the data in Value that is required to determine whether to add the data as a sample value or to actually create a sample value. Otherwise, specify void.

Work

You can save data to each instance of ReckoningCloneElement. This helps in situations such as when you want to pre-calculate particular values to make the overall dead reckoning process more efficient. Specify the types you will use to save data here. Specify void if you do not need to use this feature. This ensures that no extra memory is consumed. For more information, see Using ReckoningStrategy Instances and Work Types later in this document.

 

Constants

You can define a constant by using enum as long as the enum is still actually treated as a constant.

 

static const size_t BUFFER_SIZE

Specifies the number of sample values to store. Specify a value of at least 1 but no more than LONG_MAX.

 

Functions

 

bool Estimate (
    Value* pValue,
    const nn::pia::clone::ReckoningCloneElementBase::SampleAccessor<Sample>& accessor,
    nn::pia::reckoning::ClockValue clock,
    Work* pWork);

This function estimates the value at the specified clock time (clock) using the sample values. When this is done successfully, write the estimated value to pValue and have the function return true. Have the function return false if the value cannot be estimated. This function is called when you use the ReckoningCloneElement::GetValue() function to get values. The calculation results are cached, so this function will not be called again if you call the ReckoningCloneElement::GetValue() function again at the same clock time.

  • [out] pValue: Pointer to the variable to which to write the estimated value.
  • [in] accessor: Accessor for the sample value buffer.
  • [in] clock: Clock time to estimate the value at. This is usually the current time.
  • [in,out] pWork: Pointer to the working memory. If you specified void for Work, pass NULL here.

 

bool CheckSample(
    bool* pIsReliable,
    const Value& value,
    const SetValueArg* cpSetValueArg,
    const nn::pia::reckoning::ReckoningCloneElementBase::SampleAccessor<Sample>& accessor,
    ClockValue clock,
    Work* pWork);

This function determines whether to use the value (value) set with the ReckoningCloneElement::SetValue() function as a sample value. Have this function return true if the value will be used as a sample value. Also, specify true for the *pIsReliable parameter if you want to ensure that the sample value that is sent arrives at the receiving station. This function is called when you use the ReckoningCloneElement::SetValue() function to get values.

  • [out] pIsReliable: Pointer that indicates whether the value that gets sent as the sample value needs to arrive at the receiving station intact. If you do not specify this explicitly, the value is not guaranteed to arrive at the receiving station (in other words, packet loss may occur).
  • [in] value: The value set with the ReckoningCloneElement::SetValue() function.
  • [in] cpSetValueArg: Additional information specified when the ReckoningCloneElement::SetValue() function is called.
  • [in] accessor: Accessor for the sample value buffer.
  • [in] clock: Current time.
  • [in,out] pWork: Pointer to the working memory. If you specified void for Work, pass NULL here.

 

void MakeSample(
    Sample* pSample,
    const Value& value,
    const SetValueArg* cpSetValueArg,
    const nn::pia::reckoning::ReckoningCloneElementBase::SampleAccessor<Sample>& accessor,
    ClockValue clock,
    Work* pWork);

Creates a sample value from the value set with the ReckoningCloneElement::SetValue() function. This function is called when the CheckSample() function within the ReckoningCloneElement::SetValue() function returns true.

  • [out] pSample: Pointer to the variable to which to write the sample value when it is created.
  • [in] value: The value set with the ReckoningCloneElement::SetValue() function.
  • [in] cpSetValueArg: Additional information specified when the ReckoningCloneElement::SetValue() function is called.
  • [in] accessor: Accessor for the sample value buffer. The buffer you write here is allocated with an index of 0 when this function is called, so accessor.GetSample(0), for example, is indeterminate until pSample is written.
  • [in] clock: Current time.
  • [in,out] pWork: Pointer to the working memory. If you specified void for Work, pass NULL here.

 

void OnUpdateSample(
    const nn::pia::reckoning::ReckoningCloneElementBase::SampleAccessor<Sample>& accessor,
    int index,
    Work* pWork);

This function is called when a sample value is added. It updates the values in work memory and takes care of any other processes that may be necessary at that time. This function is called after the MakeSample() function within the ReckoningCloneElement::SetValue() function is called, or when a new sample value is received and added to the buffer in the common::Scheduler::Dispatch() function.

  • [in] accessor: Accessor for the sample value buffer.
  • [in] index: Index of the new sample value that was added. Depending on when the sample value is received, the values in the sample value buffer may not necessarily be in order from earliest to latest.
  • [in,out] pWork: Pointer to the working memory. If you specified void for Work, pass NULL here.

 

ReckoningCloneElement Packet Loss

After a MakeSample() function creates a sample value, it is sent to the receiving station, where it is added to the sample value buffer. In good communication environments, the sample value buffers of the sending station and the receiving station match (except for the time offset). These two buffers may not match, however, if some sample values do not arrive at the receiving station due to packet loss or they arrive in a different order. Ideally, the Estimate() and CheckSample() functions can do their respective jobs and estimate the target value relatively accurately even when a certain amount of packets are lost.

Specify true for the *pIsReliable parameter in the CheckSample() function to guarantee that sample values arrive at the receiving station. Note that if the sample value buffer of the sending station overflows when a new value is added before the sample value arrived safely, no more sample values will be sent to the receiving station. In other words, the sample value buffers of the sending station and the receiving station may not necessarily match even if you specified to use reliable communication for all of the sample values. Make sure that you manage the sample value buffer of the sending station appropriately to prevent this from happening. For more information about guaranteeing data arrival, see PiaClone - Guaranteeing That Data Arrives Safely in 8.3. PiaClone Detailed Specifications.

 

Using ReckoningStrategy Instances and Work Types

You can add a single instance of ReckoningStrategy to several different instances of ReckoningCloneElement. This enables you to call the functions in your reckoning strategy each time a new value is available to them (these values are passed as parameters) to calculate the positions and orientations of game objects.

On the other hand, the CheckSample() and Estimate() functions (which are called each time the ReckoningCloneElement::SetValue()and ReckoningCloneElement::GetValue() functions are called, respectively) are usually called more often than new sample values are added. To reduce the number of CheckSample() and Estimate() function calls and minimize some of this overhead, pre-calculate and save the values that can only be estimated from the sample values when they are updated. (Put these processes in the OnUpdateSample() function.) If you want to save these pre-calculated values to an instance of ReckoningStrategy, however, you must create an instance of ReckoningStrategy for each instance of ReckoningCloneElement.

If you instead save this pre-calculated data in a ReckoningStrategy::Work-typed buffer in an instance of ReckoningCloneElement, you can share the associated ReckoningStrategy instance with other instances of ReckoningCloneElement while the data is pre-calculated.

Creating many different individual instances of ReckoningStrategy is still a perfectly valid solution. However, during actual game development you may find yourself adding parameters to ReckoningStrategy that determine how often sample values are added and then adjusting those parameters manually to try to achieve a good balance between the accuracy of your reckoning strategy and the resulting amount of communications sent. Choosing the route where you can share an instance of ReckoningStrategy instead also enables these parameters to be shared, which can simplify the manual adjustment and balancing process.