-
Notifications
You must be signed in to change notification settings - Fork 38
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Multiple problems when using with Unity/MacOS #8
Multiple problems when using with Unity/MacOS #8
Comments
You are right, the signature was incorrect, it should be passed by reference. Going to investigate other problems too. |
I think this should be resolved in the native library, we can simply zero out/reset all global data at deinitialization with Other game engines might be affected by this as well, since usually libraries there are loaded once, and global data/variables will persist in memory while the application remains alive. @fletcherdvalve What do you think about this? |
It appears the problem is on C++ end. I have 704 bytes for the StatusInfo instead of 712, which probably means that some C++ defaults on Mac are to blame. I obviously didn't make any changes to the library. |
What compiler are you using? |
AppleClang 11.0.3.11030032 |
I have a feeling that this is because not all integer types are strictly set to exact-width, and some of those are compiler-specific, but I'm not sure. I'll move this issue to the native repository if @fletcherdvalve doesn't appear here next Monday-Tuesday. |
Can you figure out where the difference is coming in? |
I might be wrong, but, probably what is affecting the layout is: |
But his size is 8 bytes smaller, right? Given that those int fields are 4 bytes on all platforms I know of, this would imply that they are 0 bytes on Apple clang. :) I understand that it is legal for the C spec for int to be a size other than 4 bytes. I am just not aware of any ABI we care about that decided to make what would be an absolutely disastrous decision. It's got to be some alignment thing or some enum or something. |
Yea, hard to diagnose this without the compiler and OS (I don't have it). So @staszain it would be great if you can help us to determine what exactly is affecting the layout. |
I will be happy to provide any assistance you need from my side. As I mentioned I believe the first shift comes from StatusInfo.connection field, which is UInt32 as declared in Valve.Sockets. It takes either 8 or 4 bytes depending on Pack attribute parameter and the entire content of the ConnectionInfo structure is shifted 4 bytes depending on that. I will double check. |
Yes, it looks like it is exactly the case. If I set StatusInfo StructLayout.Pack attribute to 8, then the content of StatusInfo.connectionInfo.identity.type, which comes right after StatusInfo.connection, is already shifted by four bytes. Thus I have the content of SteamNetworkingIdentity.m_cbSize in SteamNetworkingIdentity.m_eType field instead of its proper value. |
We are trying to determine why this happens? What you are explaining is about the managed side, but we have the problem with the native one since the layout is screwed there? |
Well, my idea was that this happens because with this particular compiler the structure SteamNetConnectionStatusChangedCallback_t is packed in such a way that SteamNetConnectionInfo_t m_info is aligned to 4 bytes instead of 8 in case of other compilers Sorry, I'm not 100% sure that this is what you asking. I'll try to research it more on my side. |
Can you please check side by side which fields are causing this? It seems that the managed side is correct in your case (it should be 712 bytes), but the native one is 704 bytes in size. |
Well, the idea was that if Pack attribute fixes this on the managed side then it is SteamNetConnectionStatusChangedCallback_t.m_hConn that causes the problem. It is packed to 4 bytes instead of 8. I will try to find where the other 4 bytes diff comes from. But my guess it is m_eOldState. |
Result: |
What confuses me is that SteamNetworkingIdentity is clearly aligned to 4 bytes. I think the cause of this might be that some compilers try to align inner structures to 8 bytes within a parent structure, while other compilers do not. In this case adding some padding manually should solve the problem and not break other platforms. Something like:
Unfortunately I cannot verify this on other platforms. |
Yea, I think this should work. In my case without any manual padding |
Surprisingly |
I am very afraid of changing this struct, since it has been shipped in a Steamworks SDK. That means I am bound to support that ABI essentially indefinitely. If I change this struct I will have to introduce a backward compatibility layer or else break the games that are using the current version of the struct. Another complication is that we have to use the same struct packing on both 32-bit and 64-bit within the same platform, because these are serialized in some cases between 32-bit and 64-bit processes. So where things have ended up is that structs are packed the same on the same platform even if bitness is different. But structs are NOT necessarily packed the same on different platforms. That is sort of anti the whole concept of .NET, where you have some virtual machine bytecode that can run anywhere. But that is the unfortunate state of affairs on Steamworks. We have to have platform specific binaries, even .net binaries.
|
That makes me sad... Should we close this issue and open/plan it as an enhancement in the native repository? |
Yeah, it is pretty crappy. But now that I think more about it, though, it's probably pretty common for structs to be packed differently on different platforms, right? Also having an ABI that you are stuck with and that you cannot change doesn't seem that unusual -- outside of opensource projects that is. Perhaps the only thing that is unusual about this code is that we change the packing to ensure that 32-bit and 64-bit behave the same, but I don't think that really changes anything relevant to this problem. So surely there are standard workarounds for this, right? Is it possible to have the managed code use different packing on different platforms? Seems like the the C# Steamworks wrappers must have hit this issue before. Again, I can't imagine that what we're doing here is really unusual. This has to be a common problem. |
I could clear that pointer on shutdown, seems fine. You can set the debug output callback before initialization, and actually this is really useful. So I don't want to clear it on init. |
To be honest, this is the first time when I see something like this. My customers are using a lot of native libraries across platforms (C and C#) on Windows/Linux/macOS, and this was never an issue with structures. Their sizes were always consistent across compilers with MSVC/GCC/Clang. In .NET, we can't determine a platform at compilation time, only during the runtime, which doesn't help here, unfortunately. Managed assemblies are unified before JIT. Except asking a user to manually define symbols in accordance with a platform if it's not Unity where they are automatically defined. |
I'm gonna throw in some padding fields to force the alignment. We can fix this, at least in the opensource code. @staszain , can you confirm the values you currently have for:
|
output:
|
I was working on inserting the padding fields, and then it hit me. Why not just use #pragma pack (4)? This is honestly what I wish we could do in Steamworks, but that ship probably sailed in 2010 or so. @nxrighthere, if I set #pragma pack(4), that will actually cause the Windows packing to change, because it is currently 8. But it'd easy to make the C# match this, right? In fact, I think for all the structures we care about, 4 results in no padding anywhere, and so it is equivalent to 1. |
Looks like no matter what I do, the structs on 32-bit and 64-bit cannot be the same size, because e.g. SteamNetworkingMessage_t has a pointer member. I don't know how that works with C#. |
@fletcherdvalve Yes, it's not a problem to match it in C#, the size of the pointer is depends on the architecture of the system. |
So C# assemblies will dynamically adjust the offsets of members in structs when they are loaded, depending on the architecture? Whoa. |
Yes, this is one of the advantages of the virtual machine with just-in-time compiler. |
Steamworks SDK has found itself in a really bad place having to support ABIs that differ on different platforms. That didn't matter in the days when everybody compiled all of their binaries per platform, but it breaks the "write once, run anywhere" ideal. We don't have to support old ABIs so let's just pick a packing and use it everywhere. (But note that we do have stucts with pointers in them, so our structs will vary between 32-bit and 64-bit.) nxrighthere/ValveSockets-CSharp#8
@fletcherdvalve So we are always |
@staszain Can you confirm that the latest version from the master branch works for you? |
Yeah, always 8 now. Since I am basically just arbitrarily picking, I decided to arbitrarily pick compatibility with Windows. |
Hi @nxrighthere, what is the proper way to use this library? |
I've built the lib from the latest commit - it looks like the problem is gone. Thank you very much everyone! |
@ArnaudValensi From the release section, you should always use the version of the native library that specified there (see below changes). The source file from master might or not be compatible with the latest changes in the master of the native library, right now it's compatible. |
@ArnaudValensi |
Thank you very much for your answer and for your work. |
@ArnaudValensi It's Fletcher doing the job, I'm just matching stuff to changes that he's making. 😸 |
I could probably cherry pick and make a release. Or, just release what it is at master HEAD right now. There's some P2P work in master right now that isn't really complete, but I think everything still works. Let me know if it would help to make an official release. |
So thanks to you both @fletcherdvalve and @nxrighthere 🙏🙂 |
Steamworks SDK has found itself in a really bad place having to support ABIs that differ on different platforms. That didn't matter in the days when everybody compiled all of their binaries per platform, but it breaks the "write once, run anywhere" ideal. We don't have to support old ABIs so let's just pick a packing and use it everywhere. (But note that we do have stucts with pointers in them, so our structs will vary between 32-bit and 64-bit.) nxrighthere/ValveSockets-CSharp#8
Hi, I'm evaluating this library for some time now in Unity. I managed to import it into the project with some issues (for some reason it doesn't load the first time you run the game), however it doesn't work out of the box for me. I've compiled the library from 1.1.0 and took the same version of the bindings. Here are the problems I've encountered so far:
client.DispatchCallback(status) is getting called with info structure containing garbage. It looks like there is a problem with marshalling. I was able to fix this with adding "ref" to the StatusCallback declaration:
public delegate void StatusCallback(ref StatusInfo info, IntPtr context);
I'm not an expert in c# marshalling and not sure this is a right fix and the system is now working as it supposed to, or is it going to break other platforms.
Once StatusCallback is changed, the structure still seems to be off. It appears that some of the field values are shifted to neighbour fields. I was able to fix this by adding StructLayoutAttribute.Pack = 4 to StatusInfo structure declaration:
[StructLayout(LayoutKind.Sequential, Pack = 4)]
public struct StatusInfo {
...
as it looks like "connection" variable takes whole 8 bytes instead of 4. Not sure why Unity/Mono has a different default Pack setting, but I think this should fix the problem for Mono and not break other platforms.
Just a suggestion to mention deinitialization procedure, especially if you set a debug callback like described in the "Usage":
Library.Initialize();
NetworkingUtils utils = new NetworkingUtils();
utils.SetDebugCallback(DebugType.Everything, debug);
you absolutely MUST set it to None when you're done:
utils.SetDebugCallback(DebugType.None, null);
Library.Deinitilize();
otherwise Unity will freeze on the next launch (probably because debug callback is now invalid and crashes silently somewhere inside).
The text was updated successfully, but these errors were encountered: