Discussion:
HELP! Is it a bug or what?
(too old to reply)
Andreas Jung
2005-07-23 22:36:01 UTC
Permalink
Hello,
I am currently about to create the input-layer of a flight simulator project
in managed DirectInput using C++/CLI Express Beta 2. Due to the nature of
flight simulators, it is absolutely necesary to have fully assignable axes,
e.g. to utilize a Saitek X45 (my test-joystick) with 6 axes to full extent.

I've come up with a pretty clever approach to store the enumerated
axes/buttons in a Hashtable (err... Generic::Dictionary) with the DI-Offset
as key. (see code snippet below).

This works ... well ... for my the X/Y-Axis and the rudder plus around 6
buttons. My throttle and some other axes DO NOT WORK, neither some buttons
with ID over 6 do.

I have really no idea why, but it *feels* like an old 4-axes/few-buttons
old-school-joystick.

The actual problem is: During initialization, the throttle is stored with a
Offset of "8"; during polling, no events with an offset of "8" are found in
the buffered joystick-data. So the assert ("assert( data->Offset != 8);")
never breaks, altough it should when the throttle is moved. Other axes work
fine.

The actual throttle-value-reading in the Joystick-State-Structure looks
correct; but I cannot use the Joystick-State, as there is no connection
between the enumerated axes/buttons and the Joystick-State, other than the
offset (AFAIK I cannot simply extract an offset-integer in managed world).

My question is: is it a bug or what am I doing wrong, that I cannot receive
*all* buffered joystick-data, but only the first few axes/buttons. Is it
wrong usage of the API? Is it a missing property of the device not set during
initialization?

Please help me! This problems really becomes a serious bottleneck of my
timeframe! Documentation of Managed DirectInput is not really a
documentation, and there are even no tutorials out there, which go beyond the
question how to access the basic Joystick-State-structure...

-Andreas Jung


So as promised, below the code-snippet. It should be pretty self explaining,
even though the declarations are not fully included.

---snip---
C++/CLI Express Beta 2 code:

namespace I_DONT_TELL_THE_NAME_OF_MY_PROJECT_SORRY
{
InputDeviceHandler::InputDeviceHandler( DirectInput::DeviceInstance^
deviceInstance, Windows::Forms::Form^ cooperativeForm,
RegisterDeviceAxisEvent^ registerDeviceAxis, RegisterDeviceButtonEvent^
registerDeviceButton )
{
// Create device from given device-type-guid (temporariliy assumed to be a
joystick!)
m_Device = gcnew DirectInput::Device( deviceInstance->InstanceGuid );

// Get a pointer to a friendly name of the device
String^ joyName = m_Device->DeviceInformation.InstanceName;

// This hashtable is going to hold all device-object-instances in use
// Key: DI-offset in the state-structure
// Value:
// value class DOIHashValue
// {
// public:
// enum class DOIHashValueType
// {
// Button,
// Axis,
// Unknown
// };
// public:
// DOIHashValueType Type;
// Object^ DataPtr;
// };
m_DOIs = gcnew Generic::Dictionary<int, DOIHashValue>();

// Enumerate all device-object-instances and save in the m_DOIs-hashtable
for each( DirectInput::DeviceObjectInstance doi in m_Device->Objects )
{
if( ( doi.ObjectId & (int)DirectInput::DeviceObjectTypeFlags::Axis ) != 0 )
{
// Set range to neutral - we handle joystick-curves later on our own
m_Device->Properties->SetRange(
DirectInput::ParameterHow::ById,
doi.ObjectId,
DirectInput::InputRange( -10000, 10000 ) );

m_Device->Properties->SetSaturation(
DirectInput::ParameterHow::ById,
doi.ObjectId,
10000 );

m_Device->Properties->SetDeadZone(
DirectInput::ParameterHow::ById,
doi.ObjectId,
0 );

// Add axis
// -add to the hashtable with key=offset
// -put a float-variable into the heap, where others can point to as well
// -the name of the axis and it's pointer go to the owner
DOIHashValue newValue;
newValue.DataPtr = gcnew float();
newValue.Type = DOIHashValue::DOIHashValueType::Axis;

m_DOIs[doi.Offset] = newValue;
registerDeviceAxis( doi.Name, joyName, (float^)newValue.DataPtr );
}
else if( doi.ObjectId & (int)DirectInput::DeviceObjectTypeFlags::Button )
{
// Add button
// -add to the hashtable with key=offset
// -put a "ButtonState-Enum"-variable into the heap, where others can
point to as well
// -the name of the button and it's pointer go to the owner
DOIHashValue newValue;
newValue.DataPtr = gcnew ButtonState();
newValue.Type = DOIHashValue::DOIHashValueType::Button;

m_DOIs[doi.Offset] = newValue;
registerDeviceButton( doi.Name, joyName, (ButtonState^)newValue.DataPtr );
}
}

// Set final device properties
m_Device->Properties->AxisModeAbsolute = true;
m_Device->Properties->BufferSize = 256;
m_Device->SetCooperativeLevel( cooperativeForm,
DirectInput::CooperativeLevelFlags::NonExclusive |
DirectInput::CooperativeLevelFlags::Background );
m_Device->Acquire();
}

InputDeviceHandler::~InputDeviceHandler()
{
m_Device->Unacquire();
m_Device->Dispose();
}

void InputDeviceHandler::Poll()
{
// Just make sure everyone is awake
m_Device->Poll();

// Let DI point to the buffer of latest input-events
DirectInput::BufferedDataCollection^ bufferedDataCollection =
m_Device->GetBufferedData();

if( bufferedDataCollection != nullptr )
{
// Enumerate all input-events and handle them
for each( DirectInput::BufferedData^ data in bufferedDataCollection )
{
if( (Object^)data != nullptr )
{
// --------------------------------------------
// !!! ATTENTION !!!
// Debugger told me that 8 is the offset of my
// slider-axis (the throttle of my Saitek X45).
// The throttle is not recognized, so this
// assert never breaks!
// --------------------------------------------
Diagnostics::Debug::Assert( data->Offset != 8 );

// If the key hasn't been registered during
// intialization, the key has been classified
// as "not of any use" (=other than axis/button).
if( !m_DOIs->ContainsKey( data->Offset ) )
continue;

// Look into the bucket, wether the offset
// belongs to a button or axis.
DOIHashValue value = m_DOIs[data->Offset];

if( value.Type == DOIHashValue::DOIHashValueType::Axis )
{
// Axis-value is scaled and put into the heap, where
// others can look up the axis's value.
*(float^)value.DataPtr = data->Data / 10000.0f;
}
else if( value.Type == DOIHashValue::DOIHashValueType::Button )
{
// A button can be neutral (up), currently pressed, or
// released (one trigger-cycle: (up-)down-up -> released).
if( data->Data == 0 )
{
if( *(ButtonState^)value.DataPtr == ButtonState::Pressed )
{
// Button was down and is now up, thus it was pressed an released
*(ButtonState^)value.DataPtr = ButtonState::Released;
}
else
{
*(ButtonState^)value.DataPtr = ButtonState::Up;
}
}
else
{
*(ButtonState^)value.DataPtr = ButtonState::Pressed;
}
}
}
}
}

// Don't make the GC's life too hard...
delete bufferedDataCollection;
}
}
Andreas Jung
2005-07-28 13:52:03 UTC
Permalink
I found out, that the throttle of my X45 has an Offset of 8 during
device-object-instance-enumeration.

The actual value of the trottle is found in in Slider[0], which has an
actual offset of 24.

This explains, why there is no throttle-reading, as my approach is
DOI-Offset-driven.

Nevertheless, I pretty much think that this is a bug, because 8 != 24. This
shouldn't be!
Andreas Jung
2005-07-28 17:28:04 UTC
Permalink
After I noodled over the problem, I could image that this might be a bug in
the managed Device::Objects-collection.

I found out that the _offset-values_ of all device-objects-instances in the
Device::Objects-collection seem to be sequential; it looks like as if the
offset-values are simply increased by 4 at each iteration; which may not
necesarily reflect the actual "alignment" of the DOIs in the
JoystickState-structure.

I speculate, that this is probably due to a "lousy" port of the unmanaged
DOI-enumeration-method, which is originally a callback-enumeration, and not a
collection.


I will eventually try to verify my thesis against unmanaged C++-code and
look, what the results of the DOI-enumeration is then (especially the
offsets).

Unfortunately I am not sure if I am able to run native C++ code because I
solely operate VC 2005 Express Beta II (which has a lack of platform-sdk),
because my old VS 2002 Academic has been actived too often after all those
harddrive-formats over the years ("thanks" by the way!), and I am too lazy to
battle myself with the MS-telephone-ladies, as I will buy VS 2005 by the end
of this year anyway.


Hmm, is anyone of you MS-MVPs or "officials" listening? Any chance that
someone hears my problem or any "possibility" to suggest this as a bug? Or
any statement that proves me wrong?
Andreas Jung
2005-07-28 22:16:03 UTC
Permalink
Hmm ... after one hour of forcing myself back into the unmanaged world, I had
to find out, that unmanaged DOI-Enumeration results the same offsets as the
managed DOI-Collection.

I do feel pretty screwed up right now and I am kinda out of options.
Andrew McDonald
2005-07-28 22:33:37 UTC
Permalink
Post by Andreas Jung
Unfortunately I am not sure if I am able to run native C++ code because I
solely operate VC 2005 Express Beta II (which has a lack of
platform-sdk),
because my old VS 2002 Academic has been actived too often after all those
harddrive-formats over the years ("thanks" by the way!), and I am too lazy to
battle myself with the MS-telephone-ladies, as I will buy VS 2005 by the end
of this year anyway.
Can't help with your bug, but you should still be able to write native
C++ apps. The latest version of the platform SDK can be downloaded from
Microsoft, or failing that you could just point VC at the include and
lib directories for your 2002 installation (even though that program
won't execute if you say your activation thing has expired, the headers
& libs should still work I think?)

If you try this, does the bug still appear in a managed app?

Andrew
Andreas Jung
2005-07-29 07:44:02 UTC
Permalink
Tried this.

IT IS NOT A BUG. IT IS MY FAULT.

Managed/Unmanaged show the same behavior. After digging myself into the
*unmanaged* DI-reference, I found out, that the offsets of the
device-object-instances are not the same as the offsets of buffered data, as
one offset points to the raw-device data, and one points to the current
format in use (?).

Now I am pretty much again at the starting point of my task with the little
difference, that I don't have a plan.
Still, I want to "connect" the enumerated axes to actual states. There is a
unsolved thread some weeks "below", it's "Problem getting joystick axis
values" by Dahl. He seems to have exactly the same problems.

Maybe I will have a look at action mapping, but I am pretty much against
forcing the user in this config-dialog.
Andreas Jung
2005-07-29 22:19:08 UTC
Permalink
THE SOLUTION

First of all, it's one small step for man, but one giant leap for managed
mankind. After all of my researches over the last few days, I am pretty sure
that I am the first individual, who tried this and achieved it successfully.

The solution is to build a custom data-format, which exactly reflects the
enumerated device-objects. This way, the enumerated offset match the buffered
offsets.

So,

----

Generic::List<DirectInput::ObjectDataFormat>^ odfList = gcnew
Generic::List<DirectInput::ObjectDataFormat>();

int currentOffset = 0;

for each( DirectInput::DeviceObjectInstance doi in m_Device->Objects )
{
if( ( doi.ObjectId & (int)DirectInput::DeviceObjectTypeFlags::Axis ) != 0 )
{
...
DirectInput::ObjectDataFormat odf;
odf.Flags = 0;
odf.Offset = currentOffset;
odf.SourceGuid = doi.ObjectType;
odf.DeviceType = doi.ObjectId;

currentOffset += 4;

odfList->Add( odf );
... }}


DirectInput::DataFormat dataFormat;
dataFormat.Flags = DirectInput::DataFormatFlags::AbsoluteAxis;
dataFormat.ObjectDataFormat = odfList->ToArray();
dataFormat.DataSize = currentOffset;

// Set final device properties
m_Device->SetDataFormat( dataFormat );

-----

As you will notice, when filling the "odf"-structure, the member-names of
"odf" and "doi" do not really match each other. It was really hard to figure
out the correct assignments, which took me one evening. It was that hard,
because there is nearly no managed documentation, and the well documented
unmanaged structures have different names than the managed ones.

Nevertheless, it works now. I still have to do this for the buttons/povs,
but I really hope it won't generate too much trouble (a.k.a. debug-messages).
Loading...