[HN Gopher] Fixing stutters in Papers Please on Linux ___________________________________________________________________ Fixing stutters in Papers Please on Linux Author : rdpintqogeogsaa Score : 550 points Date : 2022-01-02 11:20 UTC (11 hours ago) (HTM) web link (blog.jhm.dev) (TXT) w3m dump (blog.jhm.dev) | reallifez4 wrote: | the_af wrote: | This is interesting. I never noticed these pauses using the | native port from GOG on Ubuntu. I'm very sensitive to this kind | of thing (low refresh rates on CRT monitors used to drive me | crazy when nobody else noticed). | | Will have to fire up my copy again. A good excuse to play this | marvelous game again. | reallifez4 wrote: | arghwhat wrote: | The issue has been identified before, but seems like it stalled: | https://gitlab.freedesktop.org/libinput/libinput/-/issues/50..., | https://patchwork.kernel.org/project/linux-input/patch/20201.... | CyberRabbi wrote: | What the proposed patch does is delay a specific latent | operation to an asynchronous context so that close() doesn't | block on that operation (which is freeing some memory). | | The proposed patch isn't a comprehensive fix, it admits there | are still other sources of relatively high close() latency. | | So that got me thinking, there is no way to fix this "bug" | because there is no specification on how long close() should | take to complete. As far as we are promised in user-land, | close() is not an instantaneous operation. close() is a | blocking operation! Even worse, it's an IO operation. | | So now I think the bug is in the application. If you want to | avoid the latency of close() you should do it asynchronously in | another thread. This is similar to the rule that you should not | do blocking IO on the main thread in an event-loop based | application. | londons_explore wrote: | close() is typically a blocking operation. But when it | happens in devfs, procfs, tmpfs, or some other ram only | filesystem I expect it to be fast unless documented | otherwise. | CyberRabbi wrote: | > I expect it to be fast unless documented otherwise. | | Logically you should expect it to block indefinitely unless | documented otherwise. The exception would be completing | within a time bound, the rule is blocking indefinitely. | loeg wrote: | > Logically you should expect it to block indefinitely | | Frankly, that's completely insane. It should block if and | only if there is actual io in flight which could produce | a failure return that an application needs. Syscalls | should be fast unless there is a very good reason not to | be. | CyberRabbi wrote: | > It should block if and only if there is actual io in | flight which could produce a failure return that an | application needs. | | Blocking simply means that the specification does not | guarantee an upper bound on the completion time. There is | no other meaningful definition. POSIX is not an RTOS | therefore nearly all system calls block. The alternative | is that the specification guarantees an upper bound on | completion time. In that case what is an acceptable upper | bound for close() to complete in? 1ms? 10ms? 100ms? Any | answer diminishes the versatility of the POSIX VFS. | | > Syscalls should be fast unless there is a very good | reason not to be. | | I think this is an instance of confusing what should be | with what is. We've been through this before with | O_PONIES. The reality is that system calls aren't "fast" | and they can't portably or dynamically be guaranteed to | be fast. So far the only exception to this is | gettimeofday() and friends. | | Robust systems aren't built on undocumented assumptions. | Again, POSIX is not an RTOS. Anything you build that | assumes a deterministic upper bound to a blocking system | call execution time will inevitably break, evidenced by | OP. | virtue3 wrote: | Very similar to people using node.getenv in hot sections | of code and the resulting not understanding what's | happening. | | https://github.com/nodejs/node/issues/3104 | | When you call out to the sys or libc things are going to | happen and you should try and be aware of what those are. | fao_ wrote: | Sorry... what? Why the hell was an application using | env() to carry application state?! | | The environment list is created at init, it's literally | placed right behind the C argument list as an array -- | AUXV if you want to go read the ABI Specification for it. | | Therefore, anything you grab using getenv() can be | considered to be static (Barring use of setenv), so the | proper and correct thing to do is shove the things you | need into a variable at init. Unless you yourself are | editing it, but you should still use a variable because | variables are typed and getenv is not (Thinking along the | lines of storing port information, or whatever, where you | need to parse it into a string to get it into the | environment, and then need to parse it out of a string). | For things like $HOME, those only ever change once, and | you should really have a list of those that you check, | because you will want to check XDG_HOME_DIR, and a few | other areas. So you will want those in a list anyway, | might as well do it at creation time when the data is | fresh. | | Anything you set with setenv() only alters the your | environment state, and that will carry down to newly | created children at creation time. So the only reason I | can think of why anyone would do this would be to | communicate data to child processes. Except there are so, | so many better and non-stringly typed ways to do this, | including global variables. Child processes inherit | copies(?) of their parent's state, you can _just use | that_ , so there is literally, NO reason ever to do this. | thelopa wrote: | ... unless you intend to exec after forking | fao_ wrote: | Sure, but just use execvp and it's a damn sight safer | because you know exactly the state of your child's | environment state. You can see this in the CERT C coding | guidelines: https://wiki.sei.cmu.edu/confluence/display/c | /ENV03-C.+Sanit... | | also ENV02-C comes into effect, as well, if your program | is invoked with | SOME_INTERNAL_VARIABLE=1 PORT=2000 ./prog | | then you try to invoke your child with: | setenv("SOME_INTERNAL_VARIABLE", "2", 1); (fork | blah blah) | loeg wrote: | > Blocking simply means that the specification does not | guarantee an upper bound on the completion time. | | I don't think that's a commonly-accepted (or useful) | definition of "blocking." By that definition, getpid(2) | is blocking. | | > I think this is an instance of confusing what should be | with what is. | | Who is doing the confusing? I said "should be." Are you | saying they're fast now but should be slow? Why? | | > The reality is that system calls aren't "fast" and they | can't portably or dynamically be guaranteed to be fast. | | This isn't a portable program; it's a Linux program. The | problem isn't that close can't be portably guaranteed to | complete in some time bound; it's that Linux is adding | what is essentially an extra usleep(100000), with very | high probability, for the devfs synthetic filesystem in | Linux. | | This is entirely an own-goal; Linux has historically | explicitly aimed to complete system calls quickly, when | that does not break other functionality. It is a bug that | can be fixed, e.g., with the proposed patch(es). | | POSIX does not mandate that close blocks on anything | other than removing the index from the fd table -- it's | even allowed to leave associated IO in-flight and | silently ignore errors. It makes little sense for a | synthetic filesystem without real IO to block close so | grossly. | dagmx wrote: | CyberRabbi's definition of blocking is correct and what | I've always seen commonly accepted. | | Blocking means you don't know how long it'll take, and | you want to wait for it to finish. The only safe | assumption is that you cannot guarantee how long it'll | take. | | getpid is accurately therefore a blocking call. You don't | know how long it'll take. You can profile and make best | guesses, but you can never assuredly say how long it'll | take. | vanviegen wrote: | I'd say that the commonly accepted definition for a | blocking call is one that may depend on I/O to complete, | releasing control of the CPU core while waiting. | | By that definition, getpid() is definitely nonblocking, | though it doesn't have an upper bound in execution time. | POSIX does not offer hard realtime guarantees. | | close() in general would probably be blocking (as a | filesystem may need to do I/O), but I'd expect it to | behave nonblocking in most cases, especially when | operating on virtual files opened read-only. | Unfortunately, I don't think those kinds of behavioral | details are documented. | dagmx wrote: | A function that sleeps for 5 seconds is blocking. No IO | involved. | | Blocking just means that you're blocking your current | code till you return out of the called function. | | Anything else regarding a function call is an assumption | unless you know the exact implementation. | loeg wrote: | Every operation in a non-RTOS is blocking by this | definition, even local function calls that don't enter | the kernel, because the kernel may switch to another | thread at any time. It's utterly useless as a definition. | Much more common is to divide system calls into ones that | call depend on some external actor and those that don't. | Eg, recv() on a socket, blocking on a futex held by some | other process, or waiting on IO to some disk controller. | Getpid() is _synchronous_ but does not _block_. | CyberRabbi wrote: | Blocking in that sense is usually used in relation to | some event. E.g. sleep() blocks on a timer, read() blocks | on IO, etc. | | In the general sense, it means that the call has an | indefinite run time. E.g. "this call blocks" = "this call | could take an arbitrarily long amount of time" | | getpid() is blocking but it likely does not block on IO | (though it could as that is allowed by the spec). | dagmx wrote: | If you call getpid, or even local functions, can the rest | of your code (in a single thread) continue till getpid | returns? | | E.g if you do this inside a function (useless code) | | int pid = getpid(); std::cout << pid+2 << std::endl; | | Will the output print even if the hypothetical call to | getpid takes a second? | | If the answer is the print will wait, then it's a | blocking call. | | If it was an async call, then it could happen | concurrently or in parallel, and unless you waited, it | would continue on in a non blocking fashion. | | Waiting for a return == blocking. It may be quick but | unless the spec specifies that it must be | synchronous+non-blocking, the distinction between the two | is moot. | [deleted] | CyberRabbi wrote: | > I don't think that's a commonly-accepted (or useful) | definition of "blocking." By that definition, getpid(2) | is blocking. | | When it comes to expecting a specific duration, getpid() | is blocking. If you run getpid() in a tight loop and then | have performance issues you can't reasonably blame the | system. | | > This isn't a portable program; it's a Linux program | | But the interface is a portable interface | | > POSIX does not mandate that close blocks on anything | other than removing the index from the fd table | | And what if the fd-table is a very large hash table with | high collision rate? How do you then specify how quickly | close() should complete? 1ms/open fd? 10ms/open fd? Etc. | | It should be clear that the problem here is that the | author of the code had a faulty understanding of the | system in which their code runs. Today the issue was | close() just happened to be too "slow." If the amount of | input devices were higher, let's say 2x more, then the | same issue would have manifested even if close() were 2x | "faster." No matter how fast you make close() there is a | situation in which this issue would manifest itself. I.e. | the application has a design flaw. | loeg wrote: | > Today the issue was close() just happened to be too | "slow." If the amount of input devices were higher, let's | say 2x more, then the same issue would have manifested | even if close() were 2x "faster." No matter how fast you | make close() there is a situation in which this issue | would manifest itself. | | Close, on an fd for which no asynchronous IO has | occurred, should be 10000x faster, or more. It's unlikely | a user will have even 100 real input devices. I agree the | algorithm leaves something to be desired, but the only | reason it is user-visible is the performance bug in | Linux. | | I've worked on performance in both userspace and the | kernel and I think you're fundamentally way off-base in a | way we'll never reconcile. | CyberRabbi wrote: | > I agree the algorithm leaves something to be desired, | but the only reason it is user-visible is the performance | bug in Linux. | | The only reason it wasn't user-visible was luck. Robust | applications don't depend on luck. | | Something tells me you'll think twice before calling | close() in a time-sensitive context in your future | performance engineering endeavors. That's because both | you and I now know that no implementation of POSIX makes | any guarantee on the runtime of close() nor will likely | do so in the future. That's just reality kicking in. | Welcome to the club :) | evouga wrote: | > The reality is that system calls aren't "fast" and they | can't portably or dynamically be guaranteed to be fast. | | Perhaps, but the reality is also that the vast majority | of games and other interactive applications routinely | make blocking system calls in a tight main loop and | expect these calls to take an unspecified but | _reasonable_ amount of time. | | "It's a blocking syscall so if it takes 1s to close a | file, that's technically not a bug" is correct, but is | any player of "Papers, Please" going to be sympathetic to | that explanation? Probably not; they'll think "Linux is | slow," "Linux is buggy," "why can't Linux run basic | applications correctly that I have no problem running on | Windows or OS X?," etc. | | "Syscalls should be fast unless there is a very good | reason not to be" strikes me as a wise operating | principle, which weights usability and usefulness of the | operating system alongside being technically correct. | CyberRabbi wrote: | > "It's a blocking syscall so if it takes 1s to close a | file, that's technically not a bug" is correct, but is | any player of "Papers, Please" going to be sympathetic to | that explanation? Probably not; they'll think "Linux is | slow," "Linux is buggy," "why can't Linux run basic | applications correctly that I have no problem running on | Windows or OS X?," etc. | | I don't agree with this logic. Windows and macOS system | calls also block. The issue of people considering Linux | to be slow is not relevant to the fact that its systems | calls block. The poorer quality of Linux games, and | commercial Linux software in general, is more likely due | to smaller market size / profit opportunity and the | consequential lack of effort / investment into the Linux | desktop/gaming ecosystem. | | Now if your argument is we should work around buggy | applications and distribute hacked patches when the | developers have abandoned them for the sake of improving | user experience. I agree with that. | | > "Syscalls should be fast unless there is a very good | reason not to be" strikes me as a wise operating | principle, which weights usability and usefulness of the | operating system alongside being technically correct. | | Linux already operates by this principle. We are | examining a situation where best effort was not good | enough to hide poor application design. | touisteur wrote: | Or, io_uring the thing. One could probably wrap close() with | LD_PRELOAD and not touch the binary... | CyberRabbi wrote: | While tempting, you can't generally fix this by simply | patching close() with some function that converts it to an | unchecked asynchronous operation. If that were the case, | you could just do that in the kernel. Close() is expected | to complete synchronously. This matters because posix | guarantees that open()/pipe() etc. will return the lowest | file descriptor not in use[1]. I.e. this should work: | close(0); fd = open("/foo/bar", ...); // fd | is guaranteed to be 0 | | If you made close() just dispatch an asynchronous operation | and not wait on the result, then the code above would | break. Any code that uses dup() likely has code that | expects close() to behave that way. | | The other issue is that close() can return errors. Most | applications ignore close errors but to be a robust | solution you'd need to ensure the target application | ignores those errors as well. | | [1]: https://pubs.opengroup.org/onlinepubs/9699919799/funct | ions/V... | [deleted] | treis wrote: | >This matters because posix guarantees that open()/pipe() | etc. will return the lowest file descriptor not in | use[1]. I.e. this should work: close(0); fd = | open("/foo/bar", ...); // fd is guaranteed to be 0 | | On a multi threaded system that isn't guaranteed is it? | Meaning, another thread could call open in-between your | close & open. | loeg wrote: | What you're getting at is that an individual thread | cannot really use this property without some form of | synchronization with other threads in the process. Eg, to | use this property, other threads either do not allocate | fds, or you take some central lock around all fd | allocations. Most well-written programs do not rely on | it. | CyberRabbi wrote: | It is guaranteed whether multi-threaded or not. It's a | process level guarantee. If your application is designed | such that you don't know what your other threads are | doing then POSIX cannot help you. | satnome wrote: | Just curious, how did you nail it down to that specific issue | and patch? That seems like a great skill to have. | tankenmate wrote: | I just did a quick check the posted fix is not in the most | recent -rc branch in the public git repo. | londons_explore wrote: | This is the issue with using mailing lists... Large numbers | of perfectly good fixes, embodying many hours of effort, just | get missed and forgotten about. | | At least with GitHub PR's, every request either needs to be | merged or rejected. | Denvercoder9 wrote: | The modern trend is autoclosing PR's after they haven't had | any activity in X months, so there's not much difference | with mailing lists anymore... | loeg wrote: | I'm no fan of mailing lists, but GitHub PRs get ignored in | much the same way. | misnome wrote: | There's ignored, and there is "not aware that it's | unresolved". How do mailing list flows handle the "give | me a list of open patches"? | leonjza wrote: | I really enjoyed the debugging process here, and am glad to have | learnt about the -k flag which seems to only be available on | systems with strace version 5.5, at least for me. | | As for the patch (and my love for all things Frida [1]), I think | a call to Intercerptor.replace() after locating the symbol with | Module.getExportByName() [2] would make for a simpler patch (at | the cost of installing Frida). For example: const | sym = Module.getExportByName("lime.ndll", "SDL_SemWait"); | Interceptor.replace(sym, { onEnter: function() {}, | onLeave: function() {} }); | | [1] https://frida.re/ | | [2] https://frida.re/docs/javascript-api/#module | Narretz wrote: | Why is the engine even checking input devices so often? Shouldn't | the input device be registered via settings and then assumed to | exist when the game runs? It seems wasteful to check all input | devices every few seconds. | pas wrote: | Exactly. It should enumerate them when the player opens | settings. Or at startup. | | But even if it wants to do this, why is it doing it on the main | thread!? :( | flohofwoe wrote: | If I start the game without a gamepad attached to the | computer, and then attach the gamepad, I'd like to use the | gamepad without restarting the game. And one would expect | that polling the attached input devices should never take | hundreds or thousands of milliseconds, there must be | something seriously wrong in the Linux input device stack or | maybe in one of the input device drivers. | Vvector wrote: | Or maybe just poll for new input devices when the game is | paused, or before the game starts. | flohofwoe wrote: | Then you still have problems to handle like accidentally | disconnecting/reconnecting the gamepad when somebody | stumbles over the cable (for instance the game might want | to automatically pause if the gamepad suddenly | 'disappears' for any reason). Gamepads should be | automatically detected at any time in the game as they | are connected or disconnected. That's how it works on | game consoles, and PC games shouldn't behave any | different in that regard IMHO. | yjftsjthsd-h wrote: | The input stack is fine, this game disabled the things that | would let it work nicely (see upthread discussion of SDL | supporting udev and inotify) | smcameron wrote: | SDL should probably use inotify() on linux so the kernel can | let it know when /dev/input has changed rather than polling it. | nemetroid wrote: | SDL has three methods for detecting input devices [1]: udev, | inotify, and, as a fallback, enumerating /dev/input. | | It seems like _Papers, Please_ uses a statically linked | version of SDL, without udev or inotify support compiled in. | | 1: https://github.com/libsdl- | org/SDL/blob/d0de4c625ad26ef540166... | Sjonny wrote: | I've been wondering.. is it possible to write something to | override the statically linked functions? In this case, | most (if not all) functions have an SDL_ prefix. Would it | be possible to LD_PRELOAD a library that loads a shared | version of SDL and goes over all the function pointers to | move them point them to a new location? Is there a tool for | this? | ds- wrote: | It looks like SDL's public symbols are all global in | lime.ndll so LD_PRELOADing SDL should do what you want. | Of course it is possible that lime.ndll was built with | -fno-semantic-interposition or equivalent in which case | the functions might be called directly without going | through the dynamic linker or even (partially) inlined. | touisteur wrote: | Well if you know where to fork, you could use Intel Pin | and divert the CFG, favorite tool for binary 'patching'. | | Edit: though here if it's a problem of file enumeration | and access, I'd probably just LD_PRELOAD something to | bypass libc file access functions and return the same | result than the first time, with no delay. | cesarb wrote: | > I've been wondering.. is it possible to write something | to override the statically linked functions? | | SDL does have a built-in way to do that trick. A quick | web search tells me it's called SDL_DYNAMIC_API. | Sjonny wrote: | cool, I never knew! Somehow the game I thought it would | add a feature is still lacking it. For some reason rumble | on my xbox joystick with Enter the Gungeon never worked. | I thought it was because of an old SDL version, because | experimentation showed that. But by using the | SDL_DYNAMIC_API env and loading my system SDL the game | still not added rumble to my joystick. Ohwell. | seba_dos1 wrote: | SDL_DYNAMIC_API is a relatively recent addition (IIRC | 2014), so static SDL2 builds from before that won't work | this way. | bregma wrote: | Static linking means the features of the Linux dynamic | loader, like using the environment variable LD_PRELOAD to | pre-load a dynamic library, are not going to have any | effect. | jchw wrote: | Actually, I think the truly preferred path is to just monitor | for udev events, which SDL supports but is presumably not | enabled for Papers, Please for one reason or another. | yxhuvud wrote: | Yes, and even if it checks for new devices, it should only need | to check devices it hasn't already checked. | masklinn wrote: | Ah, but are /dev/input entries reusable? | | Let's say you have /dev/input/event{0,10}, event5 is a USB | keyboard, you unplug it, I assume event5 goes away. | | But then you plug in a controller, does this get mapped to | event11, or does event5 get reused? Is the behaviour reliable | in all versions of linux? | | You might argue that metadata should do the trick, but in my | experience, on device files, anything beyond read/write is a | crapshoot, whether metadata makes any sense is basically a | roll of the dice. | | So if you have to open device files in order to check their | identity, you might as well skip the identity bit and just | check if you're a gamepad. | | edit: per charcircuit's comment below, it looks like the | metadata of /dev/input at least are considered reliable, and | this was used to mitigate the issue by checking the mtime of | /dev/input itself against a stored timestamp: | https://github.com/spurious/SDL- | mirror/commit/59728f9802c786... | 10000truths wrote: | Isn't this the whole point of using file descriptors? As | long as you have an open file descriptor, the kernel | resource it references should remain stable. And if the | resource is unexpectedly destroyed from under the process's | nose, the file descriptor should report an I/O error the | next time you try to read or write from it. | masklinn wrote: | > Isn't this the whole point of using file descriptors? | | Opening the same file multiple times will yield different | fds, and the paths can be modified independently of the | fd. | | The goal here is to find if: | | 1. there are new input devices | | 2. which are joysticks (a category which, for SDL, | includes gamepads, so basically "has the user plugged in | a new gamepad they might want to use for the game") | | How would keeping a bunch of fds around help? | pdw wrote: | The actual SDL fix was even simpler, they now just check if | the mtime of the /dev/input directory changed: | https://github.com/spurious/SDL- | mirror/commit/59728f9802c786... | Sjonny wrote: | Upstream fixes are nice, but since the game statically | links SDL you can't put in a newer version of libSDL.so | in the game path and have it patched like that. Are there | other ways of patching statically linked binaries with | updated functions? | asdfasgasdgasdg wrote: | A lot of games will automatically switch between keyboard and | gamepad when a gamepad is connected. Perhaps this is some | automatic background function that SDL handles. | dkersten wrote: | SDL has an event for when a new gamepad is detected or | removed: http://wiki.libsdl.org/SDL_ControllerDeviceEvent | although I don't know what it does internally in order to | detect this (well, the article describes what it does). | | But you can also manually enumerate devices as in the example | here: http://wiki.libsdl.org/SDL_GameControllerOpen | fleabitdev wrote: | Windows uses a similar polling-based approach, with a warning | that you shouldn't poll for new gamepads every frame for | performance reasons: | | https://docs.microsoft.com/en- | us/windows/win32/xinput/gettin... | | In general, "notifying the program when a new device has | become available" seems to be a surprisingly difficult | problem. I've encountered trouble with multiple device types | across multiple platforms. | Sharlin wrote: | There's a reason Plug'n'Play and USB were big deals back | then. The concept of plugging in a new peripheral and it | just working, without rebooting, was rather revolutionary. | Even though the former was more aptly called "Plug'n'Pray" | in the early years... | charcircuit wrote: | SDL wants to support hotplugging where you can plug a | controller in after you have already started the game. | vlovich123 wrote: | Operating systems let you know when a device change has | happened. You can even cache this where you pool the first | time for the initial set of data and then you just check when | a device is plugged in to update your knowledge of the state | of the world. | | I would imagine that's what's done if you're running udev but | maybe SDL doesn't do that. | Sharlin wrote: | As has been noted in another subthread, SDL can do this but | apparently the particular statically linked version shipped | with Papers Please is compiled without udev or inotify | support and has to fallback to manual checking. | [deleted] | shhhum wrote: | Some time ago I've also stumbled upon this issue and found the | workaround that I've posted here: | https://www.gog.com/forum/papers_please/terrible_lags_on_lin... | | Thanks for a more proper digging. | facorreia wrote: | This reminds me of the recent Linus Tech Tips series on gaming on | Linux[1]. Their conclusion is that although many games work out | of the box (although usually not at launch), Linux is not ready | for mainstream gamers. Not many people would have the expertise | or the interest to troubleshoot the problem as OP did. | | [1] https://www.youtube.com/watch?v=Rlg4K16ujFw | spijdar wrote: | It definitely isn't. I've been a huge linux nerd since my | preteens in the late 2000s, I jumped on to squeeze more | performance out of the thoroughly mediocre hardware I had | access to. I wanted to program, and I found Visual Studio to be | incomprehensibly dense and confusing, while Linux tools were so | much simpler, with GCC, GEdit, makefiles and the like being | more to my liking. I fell deep into the rabbit hole, learned | emacs, then vim (it was more responsive on my intel atom- | powered netbook), became a "shell guru", eventually went to | college at 16 and started doing cybersecurity work/pentesting | professionally. I've even made a tiny contribution to the Linux | kernel, which I'm pretty proud of. | | All this anecdata to say, I consider myself pretty okay at | using Linux, I "prefer" Linux, but I _don 't use Linux for | gaming_. Not unless it makes sense. I play Minecraft on Linux, | and FOSS games that were developed _on_ Linux. There 's a | POWER9 desktop on my desk that runs Linux, and all my | professional and hobby work goes there. I love it. | | But any commercial games? They go on my old college-days Intel | desktop, running Win10. I can do the work to get games running | on Linux, but why bother? Like Linus says in that video, when I | have time to play video games, I really don't want to pull out | a debugger and strace and crap to do more $DAYJOB work. | | Not to say I never do that for fun. I do. I've done some work | with https://github.com/ptitSeb/box86, and that involves a | similar process. But I just frankly don't find doing it to your | average Steam game to be very fun. Sometimes the muse strikes, | usually it doesn't. | | And for your average Linux user, much less your average | _computer_ user overall, you can forget about it. IMO, unless | you have a strong ideological reason to only use FOSS OSes (and | all the power to you!), the reason you use Linux is because it | 's a vastly superior tool for certain problems. | | Playing your average commercial game is not one of them. | CorrectHorseBat wrote: | It's not, but it really has come a far way and I'm extremely | impressed. I'm kind of the other way around, I've never been | more than a very casual gamer and I'm simply not interested | in keeping a separate Windows pc or dual boot install for | games. If I can't get it working on Linux I'm not bothering | with it. Right now I can play any game I want to play with | very minimal tinkering (that probably says more about me than | the state of Wine/Proton, but still). | papersplease wrote: | pjmlp wrote: | I guess that is the kind of challenge to have Windows games on | GNU/Linux, fix them instead of playing. | voiper1 wrote: | https://www.pcgamer.com/indie-dev-finds-that-linux-users-gen... | | This dev found the linux users returned high quality bug | reports | | >Only 3 of the roughly 400 bug reports submitted by Linux users | were platform specific, that is, would only happen on Linux. | | While this post is a linux-specific bug, in general they can | end up identifying underlying bugs that affect all platforms. | the_af wrote: | Isn't this a native port though? | | I remember playing Papers Please even before it had a native | port, and enjoying it with zero problems. | pjmlp wrote: | Apparently I got that wrong, point still stands though. | shmerl wrote: | It's not like Windows doesn't have its own issues to fix. | Except developers do it anyway, because of the market size. | Windows isn't perfect or better for gaming. | qayxc wrote: | > Windows isn't perfect or better for gaming. | | This assessment depends entirely on the perspective. | | From a developer's POV, Windows definitively is the better | platform, as it's very monolithic in that you can rely on the | presence and longevity of APIs. Depending on the dev's | influence on the market and the success of the game, you even | get free optimisation, support, and bug fixes from h/w | vendors in the form of game-specific driver patches. | | From a gamer's POV, Windows has advantages as well, since | bugs are rarely OS-related and h/w vendors offer a lot more | features OOTB. | | If you love tinkering with the OS and don't care if some | titles or features just won't work, Linux is a valid option | for gaming. Otherwise Windows _is_ objectively the better | option by default, since I can rely on the games working with | all available features (e.g. multiplayer). | papersplease wrote: | Gavin Newsom and London Breed are nazis. | | The "papers please" crowd are all going to prison (hopefully much | much worse). | papersplease wrote: | Keep downvoting NAZIS! | | You know you are EVIL. You all will pay dearly for your crimes. | Aissen wrote: | strace tip of the day: you don't need lsof, strace can keep track | of open fds quite well, just use the -y flag. | -y --decode-fds --decode-fds=path | Print paths associated with file descriptor arguments. | -yy --decode-fds=all Print all | available information associated with file | descriptors: protocol-specific information associated with | socket file descriptors, block/character device number | associated with device file descriptors, and PIDs | associated with pidfd file descriptors. | shmerl wrote: | Interesting investigation. I don't remember having this problem | when playing the GOG version, but may be I didn't have a lot of | input devices? | ux wrote: | Great post, learned a bunch of things. Only one blog post, and no | RSS (yet?) unfortunately. | charcircuit wrote: | From the description of the problem (a freeze every 3 seconds) I | knew exactly what it was. You can fix it by simply upgrading SDL | as they fixed this bug 2 years ago. | | https://github.com/spurious/SDL-mirror/commit/59728f9802c786... | Diggsey wrote: | Why is it even doing this on the main thread at all? The | obvious thing would be to have a background thread polling for | changes and then sending messages asynchronously to the main | thread if a change actually occurred... | ninepoints wrote: | Because architectural simplicity is valuable and some | operating systems deliver input events on a particular | thread. | flohofwoe wrote: | The problem probably only shows up on some machines and | hasn't been noticed during development and testing. And TBH, | polling what input devices are connected should never take | more than a few microseconds, no matter how much operating | system code sits between the hardware and game code. | arghwhat wrote: | It is appropriate to use your main thread for your OS | interaction - polling fds, talking to display server, | whatever I/O you need, etc. An open/close call should never | take this long, and you should never need to make a large | amount of them in sequence after startup. | | What should not be on your main thread is any long blocking | compute, which is why rendering/game logic often goes to | another thread - although simple games could easily be | single-threaded. | dagmx wrote: | You shouldn't be doing IO on your game thread though. (Main | and game thread may differ) | lawl wrote: | > An open/close call should never take this long | | > What should not be on your main thread is any long | blocking compute | | Isn't that contradicting yourself? I'm pretty sure open() | can block. | charcircuit wrote: | The simple answer is that it wasn't needed for udev. It may | not have blown up on the dev's machine because their input | devices were different. It might be tested less than the udev | version. As the other commenter stated it's simpler to just | check every 3 seconds instead of adding threading. | salicideblock wrote: | From one of the printouts in the post, it seems that Papers | Please is using a bundled and statically linked SDL. | | So it would be the game developer that would have to update the | version of the SDL library. The binary patching done seems like | a good-enough alternative in the meantime. | | I have the feeling such bundling of dependencies is fairly | common when porting games for Linux. | charcircuit wrote: | At least for Steam you are recommended to link against | Valve's Steam Linux Runtime which is a set of dynamic | libraries including SDL. | viraptor wrote: | It's common in application of certain size and compatibility | expectations. Windows and Mac games will bundle their | dependencies as much as possible as well. Same for large | apps. Nobody wants to end to in a situation where their | relatively expensive purchase doesn't work because of the | version of local libs. | ironmagma wrote: | Does bundling dependencies imply static linking, though? | Why can't they just dynamically link to the bundled | dependency? | edflsafoiewq wrote: | SDL has its own "dynapi" layer, where you can override it | with your own copy of SDL even if it was statically linked: | https://github.com/libsdl-org/SDL/blob/main/docs/README- | dyna... | MayeulC wrote: | Just pray they didn't alter SDL like factorio does: | https://news.ycombinator.com/item?id=27246164 | ck45 wrote: | Is the version statically linked recent enough to support | it? Also, can't decide if it's genius or insane, that extra | layer of dynamic linking... | charcircuit wrote: | According to [1] it was added (but not released) January | 8th, 2014. Papers Please came out on Linux February 12, | 2014 so I'd figure it's not in there unless the version | of SDL was updated in a later update. | | [1] https://old.reddit.com/r/linux_gaming/comments/1upn39 | /sdl2_a... | | Edit: (what I believe to be) This freezing bug was only | added 3 years ago, so it might actually have it. | AshamedCaptain wrote: | Why is udev not used in this case? | charcircuit wrote: | Because this is the fallback when you compile without udev | support | AshamedCaptain wrote: | I can guess that, but I was wondering why ... Is there a | distro without udev? The steam/whatever sandbox does not | support udev? | Sjonny wrote: | It's not the system that is not supporting udev, it's the | choice of the game developers how they compiled SDL .. | without dependencies, and so without udev. | mschuster91 wrote: | That's standard for game devs in the Linux world. The | less dependencies you have to rely on the distribution | for, the better - Windows stuff is either already present | or shared OS-wide with binary backwards compatibility | (=DirectX), so you can get away with shipping stuff that | has a chance to run even 25 years in the future without | major modification. | iqanq wrote: | This is one of the reasons I use Gentoo in my desktops: you can | run a "stable" (as in old) system, but pull a more recent | version of a library or application if you need it. For | example, I remember having problems accessing files that I had | stored in my mobile phone. Solution: updating libmtp and libmtp | only. I suppose you can't do this in a distro such as Debian | without upgrading half the packages. | | Are there more distros that allow you to do this? | teddyh wrote: | If you are advanced enough to run Gentoo, you should be able | to use Debian and force an install of (or re-compile | yourself) a new package of the newer version, working around | the fact that the official newer version would otherwise | require other new packages. | vlovich123 wrote: | You could. But the amount of time it takes is significantly | more than yay -S <package> or emerge <package> vs the hell | that this poses on Debian (not to mention the dependency | hell you can run into) | yjftsjthsd-h wrote: | I don't know the situation on Gentoo, but partial updates | are explicitly unsupported on Arch, not least because | they don't do stable ABIs; Debian should have a much | easier time upgrading just one package. | elevader wrote: | It's not necessarily recommended to mix stable and | testing but it mostly works fine in my experience. I'd | guess Gentoo gets around quite a few problems as | everything is compiled from source. So updating a single | libary would cause a rebuild of everything that depends | on it. | | Gentoo also has the concept of "Slots", so you could have | multiple versions of the same libary installed and | packages will choose their version to build against | accordingly. | vlovich123 wrote: | Having used Arch and Debian, I've definitely had an | easier time installing the latest version of arbitrary | packages on Arch. Something like SDL is part of base and | thus is already running latest. | ximeng wrote: | Fun write up. Here's another example of a binary patch to fix a | Linux game issue: | | https://steamcommunity.com/app/333300/discussions/0/26463606... | CmdrKrool wrote: | This looks very like a problem I encountered some time ago | running the closed source 3DO emulator "Phoenix Project", and | similarly the open source "FreeDO" project that it was forked | from. I narrowed it down (also using strace, IIRC) to these | programs repeatedly opening and closing the /dev/input/event* | files, and that being weirdly slow. I made a seperate little test | program just to open and close those files to confirm it. It was | only slow on my main desktop machine; while on my lesser-powered | laptop, running a practically identical Arch Linux setup, those | file operations were quick and the programs ran fine. None of | these programs use SDL. I couldn't/didn't progress any further | then, but it's good to find some pointers here for further | investigation (ie. the libinput issue). | | Funnily enough I'm pretty sure I played Papers Please on this | machine at length without problems but I think that was probably | the Windows version through Wine. | salted-fry wrote: | Hey, I know this issue! I ran into it in CK3 when it launched. | You can also work around it by running chmod go-rx /dev/input/ | while playing your game. Whether this is more or less invasive | than binary-patching the game is up for debate. | reallifez4 wrote: | reallifez4 wrote: | Traubenfuchs wrote: | Why would you not let another thread do this nasty kind of | polling and let the main loop check for a changed result, if at | all? | loeg wrote: | Close, on a synthetic fd with no I/O performed, should not take | 100ms per call. This is a Linux performance bug. | mgaunard wrote: | close can take arbitrarily long, it's a blocking operation. | | Don't ever call close on the hot path. | kaszanka wrote: | Wait, why is `close` in libpthread.so? | cesarb wrote: | > why is `close` in libpthread.so? | | That's because close() is a pthreads "cancellation point" (see | https://man7.org/linux/man-pages/man7/pthreads.7.html for | details), so it needs special handling when the process is | using pthreads. If the process does not link to libpthread.so, | the implementation in libc.so (which probably doesn't have | cancellation point support) will be used. | jchw wrote: | I believe that a few libc functions are reimplemented in | libpthread, the idea being that if you _don't_ link to | pthreads, you don't need the overhead (locking, etc.) that is | needed in multithreaded situations. Feels a bit antiquated | now... | | As for why close specifically though, that's a good question. I | wonder if it has something to do with special libc treatment of | the standard fds or anything like that. | loeg wrote: | As you can see in the disassembly, it has to do with | implementing async cancellation. I think they wrap many | blocking syscalls in the same way. | https://man7.org/linux/man-pages/man3/pthread_cancel.3.html ___________________________________________________________________ (page generated 2022-01-02 23:00 UTC)