« first day (3626 days earlier)      last day (1401 days later) » 

10:04 AM
Why when building a project with cmake i have to choose generator?
 
 
2 hours later…
nwp
11:50 AM
@0x00004 Because cmake is a meta-build-system that generates project files. Maybe some day they'll do it directly, but from what I understood that's not a goal.
There are also build systems that generate cmake files. The fun never ends.
 
 
1 hour later…
1:16 PM
Turtles all the way down.
 
nwp
@Charanor While you're probably right I don't want to accept that. You're essentially saying that the amount in your bank account defines your standing in court and that all are supposed to be equal before it means nothing. Even if that is the case my ideals say I should not give into it and fight it in any way I can.
 
I heard a story a while back of someone who was mounting a stage production of a book that happened to be getting a big-studio feature film out soon. They got a cease-and-desist notice from the film studio, and the studio lawyer told them over the phone more or less that they knew their case wouldn't hold up in court, but they also knew they had the legal muscle to keep it tied up in litigation for years through the movie's release, so the theatre producer might as well give up now. :(
 
nwp
1:39 PM
Apparently they are calls SLAPP suits and there are some countermeasures to them. Some laws protect against them and I think there are organizations dedicated to financially support people in such cases.
 
Yep. They still happen though, and fighting them still takes time and resources that a lot of folks don't have, even with extra support. In the instance of the theatre, you could have your legal costs paid, and still have no show to put on for a year while it works through court. The legal support doesn't pay the performers, stage hands, venue, etc.
 
nwp
1:54 PM
Hmm. Maybe make a media campaign. "Disney starves artists with SLAPP suit". I want to believe that you're not completely helpless in that situation.
 
that would probably delay the one that sues too, though, "can't release the show due to legal issues".
 
The one that sues typically has far more resources to wait things out.
It's attrition.
 
Yes, aslo.
 
If they even have to wait it out. The lawsuit might be one-sided. Just not sure what would prevent a countersuit. (Other than, once again, resources.)
 
Yeah. Isn't there a rule where if the one that sue pays the legal fees of the one that was sued if the one that was sued "wins"?
 
2:01 PM
Not that I know of...
 
nwp
Not if you go bankrupt before then and can't see the lawsuit to the end. Also it still ruins you because instead of games you fight lawsuits.
What? I thought that was standard.
You can't just make everyone pay lawyers until they do what you want or run out of money.
 
I mean... maybe... Not very knowledgeable about this.
I mean in the US, burying people by making them pay for lawyers is half the legal system.
 
😥
 
nwp
Has to refrain from making a comment about civilized countries
 
money == power
 
nwp
2:04 PM
Not before a court. They are specifically designed to break that equation.
 
And yet...
 
I think a lot of our systems need a design update. We're due for a patch. ;)
 
nwp
Then again in context I would be quick to make concessions. "Dear Microsoft, you are apparently unhappy with me displaying your controller in my game which is meant to show the ideal usage of my game with your controller. If you want your controller to be displayed differently I'm willing to change it accordingly." I have no clue if that would work.
 
@nwp woa... definitely not true in the US
watch "The Innocence Project" on Netflix
 
nwp
Rip
 
2:06 PM
I suspect that it is the case as long as you are in court. You need money to get there and stay there, and it is where the money == power comes from.
 
But yeah, coming back to the original controller question, I don't honestly anticipate it would cause problems. I think it's in MS's interest to be friendly to developers making games for their hardware/OS. It's a bit different from SONY if you're using the gamepad plugged into a PC - they don't have quite the same stake there.
 
not even that: if you have no money, you get assigned a public defender, and they are not always good
 
nwp
Apparently Disney executives manage just fine not spending all day every day in court.
 
often because they're too busy, but yeah it's a mess
 
I just don't have solid legal evidence to say "this is definitely safe"
 
2:33 PM
grr. Trying to pick up on a project I haven't touched for months is mentally draining...
I should have left notes on what to work on next when I picked it up...
 
Did you leave the project in a state where there were things that did not work?
 
The worst I find is leaving a project in a state where things work but I cannot fathom why they work.
 
So most of it is unimplemented.
I just need to pick something and run with it.
Yet...
@DMGregory I feel like I never know why anything works. I just thank god for black magic voodoo.
 
How do games?
 
🤷‍♀️
 
2:52 PM
@brug Write each feature on its piece of paper, put them in a hat and pick one piece of paper from the hat and implement the feature :P
 
I am at once horrified and delighted by this elegant algorithm.
 
So is there a word for the crippling anxiety you face when there is an abundance of options to choose from? But the paper in hat solution might help...
 
i bet German has a word for it
 
nwp
"Analysis paralysis" is the term. Not sure if there is a single word for it.
 
they have a word for "that feeling that your life will end soon (because you're 60 or whatever) and you haven't gotten enough done": torschlusspanik
literally "gate closing panic"
 
nwp
3:01 PM
:thinking:
You got some details wrong there :D
 
not suyrprising
wenbt from memroy
:)
 
nwp
Or maybe I got some details wrong. Now I have to check.
No, looks like you got it right.
 
user92578
Is there an existing pattern for a thread future and then an optional value that stores the result of the computation after the future has been set & consumed?
 
nwp
std::promise + std::future
 
user92578
I find very often I end up with a std::future<T> m_ValueFuture; std::optional<T> m_Value;
 
user92578
3:05 PM
And then once per frame/whatever I check the state of the future and move the value into m_Value if it's ready
 
user92578
So am I doing threading wrong? Do better languages have this thing built-in? Is this just an odd thing to want?
 
nwp
I don't really know what you want to do. Is the core issue that you can only get the value from the future once but need to be able to read it multiple times?
 
user92578
Yeah, I boot off an expensive calculation with std::async, and then based on m_Value.has_value() I can like render a loading text or display the actual content
 
user92578
Probably a cheap-out, but makes it fairly easy to make asynchronous flow eventually work with the synchronous game looping
 
nwp
Oh hey, I found that one other person in the world that uses std::async.
@Tyyppi_77 Sounds racy.
 
user92578
3:20 PM
It isn't, since I check the value of the future once per frame, and if it has a value, set it into the optional
 
user92578
Checking future state isn't racy, right?
 
user92578
std::optional<Glyph> Manager::GetGlyphForActiveDevice(const Action action)
{
    if (m_GlyphAtlasFuture.valid() && m_GlyphAtlasFuture.wait_for(0s) == std::future_status::ready)
        m_GlyphAtlas = m_GlyphAtlasFuture.get();

    if (!m_GlyphAtlas)
        return std::nullopt;

    return Glyph{ m_GlyphAtlas->get().Texture_, m_GlyphAtlas->get().Items_.Get(m_ActiveDevice->GetGlyphPath(action)).GetSource() };
}
 
user92578
There's an example, Manager's constructor launches an std::async task to load the atlas, and whenever our rendering needs to display a glyph (this happens always in sync), we check if the task is ready and store the result, after which we can use it
 
user92578
Yeah a mutable getter is ugly, but the future check and value store could be moved into a Update() function
 
nwp
3:35 PM
@Tyyppi_77 I'm pretty sure it is. It's not racy with promise.set_value, but multiple threads doing future.valid seems like a race condition to me.
 
user92578
But there is one thread setting it and one thread reading it?
 
nwp
Though ... hmmm. Just caling valid should be fine since it's const. You just can't call anything that is not const such as future.get.
@Tyyppi_77 That's the definition of a data race which is UB.
 
user92578
Right but isn't this what futures are for?
 
user92578
Calling get is supposed to block on the calling thread and wait for the value to get assigned, which then would 100% be a datarace?
 
nwp
No. You have a promise for setting and a future got getting. Those are separate objects, so you can access them from different threads. They have to share some state, but that is done in a way that doesn't produce a data race.
 
user92578
3:38 PM
Right, so doesn't std::async handle that for me?
 
user92578
I'm not assigning anything to a future, I'm just returning from a lambda
 
nwp
std::async takes the promise and sets a value eventually (void and exceptions count as values in this context). And you can do future.valid and future.get without more synchronization. But if you have one thread doing future.valid and another future.get you have a data race on the future.
 
user92578
So does that happen with std::async or am I good here? Worker thread executes lambda and returns, main thread periodically checks future as above and get()s
 
nwp
If you have a single thread doing the periodic checking it's fine.
 
user92578
All right, so now to loop back to the original question, I presume what I'm doing is strange then, is there a better pattern for this?
 
user92578
3:43 PM
Otherwise I feel like I might create a AsyncValue<T> wrapper for myself that does this and I don't have to copypaste the future checking everywhere
 
nwp
If you actually do need the feature of being able to get the future result multiple times then I guess a wrapper is the best way.
Are you using an intermediate mode GUI?
 
user92578
Yes, and while related, I don't think that's fundamental to the issue
 
nwp
Normally I would expect the lambda to send a message including the result to the GUI thread which then makes a single change, so the need to read the result more than once, or from a future ever actually, wouldn't exist.
 
user92578
Right yeah, so I have some code that dumps profiling data into structs I can easily render into ImGui buttons to visualize the frame data, so I need the data for a longer while instead of just creating a bunch of widget objects once
 
user92578
World generation is an example where that's not the case though
 
user92578
3:48 PM
I will obviously need an instance to my game world for as long as the gameplay is running, to tick it on every update and to render it on every update
 
nwp
Hmm. I'd probably have the game world data for the GUI thread and the world generator lambda that sends the generated world to the GUI thread which integrates the result with the game world data. In other words I'd try to put it into the message passing form and not poll a bazillion futures every frame.
But ... take it with a mountain of salt. You made games before, I didn't.
 
user92578
My previous game was like 99% single threaded, only Steam integration was multithreaded and only for the Steam callback parts and it was a mess
 
user92578
Hmm I'm not sure if I understand what you mean with the lambda sending the world to the main thread..?
 
user92578
All though hmm the world generation example, while relevant, isn't actually the same exact pattern, as I use a variant state machine there to separate between GeneratingWorld and PlayingWorld states
 
user92578
GeneratingWorld stores the future, checks it, and instantiates PlayingWorld with the created World if it's ready
 
nwp
3:55 PM
The state switch happens when the lambda sends the world generation data to the GUI thread. I don't think you need that future.
Assuming you have a message queue in your GUI thread. Otherwise all of what I said you cannot do (unless you add some form of message queue).
 
user92578
Oh, I currently don't but I don't think it'd be terribly difficult to slap a mutex into my event bus implementation
 
user92578
That makes sense now, this was the piece I was missing
 
user92578
All though now instead of checking a future, we're locking a mutex every frame? Is that significantly better?
 
user92578
(Unless I wizard up a lock free queue)
 
nwp
I'd say so. You must lock to synchronize with your other threads anyways in some form. This allows you to lock once per frame and is as efficient as I can imagine it. It should definitely be better than polling that future for the duration of game play, assuming you have more things you load which adds up eventually.
And I would advice against a lock-free queue. You are trading performance for a forward progress guarantee. You don't have issues with life-locks I think so the benefit doesn't apply, but still pay for the performance cost.
That is if the things I heard from cppcon are still accurate. I never actually dabbled in lock-free stuff since I just don't have the problem that it solves.
Then again I don't care about performance either.
 
user92578
4:04 PM
So besides performance, what's the actual gain here? I feel like I either end up with a global UI thread event bus (breaks decoupling totally when a lambda throws a GeneratedWorld into something global) or a class local event bus, which still requires me to write more than I feel like I should have to
 
nwp
You end up with a global GUI message queue, yes. All your worker threads get to throw their results into it. I don't see how that breaks decoupling. There is some sort of push_event function that all threads have access to but which doesn't expose much (the parameter type exposes a little, but you can keep that somewhat generic still). The workers don't know anything about each other.
The GUI thread has to be able to understand the results of each worker thread, but if your parameter is something like a function that applies a change to the game world it doesn't actually need to know anything about the result.
The benefit is that you get through it with minimal locking. A single lock per frame doesn't cost anything and arguably it isn't even overhead since some form of synchronization is required.
You do get into issues when your workers spam you with more messages than you can process, but that shouldn't happen in practice.
 
user92578
Well I mean that if I have two objects alive that generate worlds asynchronously, I now need to manage which one is active/who the posted world belongs to
 
user92578
4:21 PM
Solvable with a different message type for each object I guess...
 
nwp
Do you actually have multiple worlds in flight where the first thread that finishes decides what the world look like? That seems weird.
And I would avoid using different message types. It doesn't matter where the world comes from. Make a function that applies changes to the one game world. That's it.
 
user92578
No, I mean I would have two different objects spawning a task to generate a world
 
user92578
So both would subscribe to a WorldGenerated message
 
user92578
But how does the message know who it goes to?
 
user92578
I.e. was it the GameplayState that spawned the task or AutomatedTestState that spawned the task
 
nwp
4:34 PM
GameplayState and AutomatedTestState don't get to participate. They don't have a message queue and you can't send them messages. There are gui_thread, network_thread, loader_thread and some more, and those each have a message queue. At the start some threads get some start work, like the loader_thread gets the message to load the start screen and when it's done it sends a message to the gui_thread to display it.
Then the user clicks something which triggers displaying a loading screen and the loader gets a message to load a level, after which it sends a message to the gui_thread to display it.
I feel like your actors are way too granular.
 
user92578
So now I'm processing all thread-to-thread interactions at a global level?
 
nwp
Most threads only ever interact with the gui_thread, so it's not really all to all. Also you don't need to process anything. You just write each thread reading its own messages and sending messages to other threads.
 
user92578
I do need to process the messages, to pass the generated world from the message bus to the thing that actually uses it?
 
user92578
But you're saying that I just need to do more work
 
nwp
The processing looks like while (not message_queue.is_empty()) { message_queue.pop()(game_state); }.
It doesn't look like a lot of work to me.
 
user92578
4:39 PM
Oh, I see
 
user92578
I though we were pushing actual messages, but we're just queuing up executors to call
 
nwp
And yeah, technically there are all sorts of threads that communicated with you, but you have no clue which one or what they did.
 
user92578
So once my async task is done, it would just queue a lambda that assigns to the correct member in GameplayState or AutomatedTestState, which then gets executed in sync safely
 
nwp
Since we talked about locking once, this normal approach locks on every pop(). If you're worried about locking too often then you can lock at the start and make pop() not lock. Though ... I wouldn't expect that to make a performance difference. Locking mutexes is cheap. Contention is expensive.
@Tyyppi_77 Right. So your gui_thread and game_thread are only very loosely coupled.
 
user92578
So turns out I'm actually doing this with my content loading, they do as much as possible (file IO) async but throw OpenGL access lambdas onto the main thread which get processed once per frame
 
nwp
4:44 PM
Yup. You have to do the same with other objects like sockets too. Only the network_thread is allowed to handle sockets for example.
 
user92578
Hmm right this is starting to make sense, only annoying bit is to pass the message queue down to anything that might need it
 
user92578
Changing this doesn't look too bad though, seems that I have 6 calls to std::async in the codebase
 
nwp
In practice you will probably slip up and write socket.send(data); in your gui_thread when you should have written network_thread_send([data](Socket &s) { s.send(data); });. Also issues with data vs &data arise. It gets tricky in the details and there is no tooling help to get this correct.
I can't tell if this architecture works for a game, but it's the architecture I use for GUI programs and it's the only reasonable architecture I know of.
 
user92578
Seems fair and reasonable, I'll need to think about if I like the obscurity of this though, getting a future of T seems nice and straightforward (and helps with lifetime issues, for better or worse) but the controlled execution of the synchronized bits would definitely help to multithread a lot of things
 
user92578
Much appreciate the help BTW, I hope I don't come across as too confrontational with my questions :)
 
user92578
4:57 PM
I probably also have a few std::thread(task).detach()s around the codebase, doesn't feel like the cleanest either
 
user92578
7:11 PM
 
cool
 
Very nice! Is this playing back recorded human play, or AI-driven solving?
 
user92578
This is my recorded plays, all though I have vague plans about verifying cave "solvability" with a pathfinder playback too
 
neato!
 
user92578
The downside of human plays is that these invalidate whenever I tweak something, but my goal here is to create something nice enough to use that it won't be a big burder to replace invalidated recordings
 
7:25 PM
You might be able to digest the recordings down from exact button presses to semantic steps. "Jump from current position to position X,Y" etc. Then compute the needed input that accomplishes that same semantic move outcome given the updated jump/gravity/friction mechanics.
Wouldn't fix everything, but it might save you from having to re-record for every single 3Cs tweak. ;)
 
user92578
That sounds tricky but also like a good idea :) The main issue I want to kind of want to try to solve with this is the panic I was in at the end of GunHero development, where I was terrified of fixing bugs due to regressions / creating more bugs
 
user92578
Every moving platform / elevator / player crushing fix usually created 4-5 more
 

« first day (3626 days earlier)      last day (1401 days later) »