Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
Rocko Bonaparte
Mar 12, 2002

Every day is Friday!
Does anybody know how to work with FPlatformFileManager in Unreal Engine 5.x?

I'm trying this and platfile is null:
code:
	FPlatformFileManager fpman;
	auto platfile = fpman.GetPlatformFile(*(FPaths::GameSourceDir() + "test_platform_file"));
If I don't give it an argument, I don't actually get a pointer, and I fail because I can't copy the abstract class. If I just give it "test_platform_file" then that's also NULL.

In 4.x, you'd initialize FPlatformFileManager using a static singleton: FPlatformFileManager::Get() but that's been deleted.

Adbot
ADBOT LOVES YOU

Jabor
Jul 16, 2010

#1 Loser at SpaceChem
The ::Get method still seems to be there in the 5.x docs? What makes you think it's deleted?

Rocko Bonaparte
Mar 12, 2002

Every day is Friday!

Jabor posted:

The ::Get method still seems to be there in the 5.x docs? What makes you think it's deleted?

Visual Studio highlighted it as invalid and the hover-over stated it had been "deleted." I suppose I could have been a manly man and tried to compile it anyways.

blastron
Dec 11, 2007

Don't doodle on it!


Do you have the engine source downloaded? I spend quite a lot of time looking directly at the source to see how the engine uses its features.

e: Oh, I think I might know what’s wrong: Get() returns a reference, and you need to store it as a reference. I’m not at my work computer so I can’t check, but I would bet that what’s been deleted is the copy constructor.

blastron fucked around with this message at 18:46 on Jan 31, 2024

Raenir Salazar
Nov 5, 2010

College Slice
And yeah Visual Studio will hallucinate syntax errors with Unreal so compile anyways.

Rocko Bonaparte
Mar 12, 2002

Every day is Friday!
Ahh I guess I forget about returning references. I just never, never, never do it.

So this compiles:

code:
	FPlatformFileManager& fpman = FPlatformFileManager::Get();
	auto platfile = fpman.GetPlatformFile(*(FPaths::GameSourceDir() + "test_platform_file"));
platfile still ends up being null. I guess I can try to step debug into it and get the source if I don't have it. The first problem with a static analysis is that platfile is an IPlatformFile* and that's abstract, so I wouldn't know what to even look up.

Edit: In source-level debug, I saw it was trying to match the path I was using to modules. I thought that was really strange. I don't really understand the objective there. Maybe to access collateral included in a module or something?

It looks like if I use the parameterless version with the reference I am given from Get(), then I think I get the actual file system. I'm still messing around with it to make sure.

Rocko Bonaparte fucked around with this message at 09:50 on Feb 1, 2024

Rocko Bonaparte
Mar 12, 2002

Every day is Friday!
So I was having the damndest time figuring out the quirks of UE 5 file I/O. Videos do this-or-that that aren't entirely consistent and watching videos for that is just annoying anyways. My work has a Chat GPT license so I wrote up a prompt to demonstrate how to do it in UE5. I don't have the exact prompt here, but I asked it to check for a directory, create it if it doesn't exist, write a file with "Hello World" into that directory, then open the file again and print the contents to the log. So after a bunch of hemming and hawing, I get this:

code:
#include "Misc/FileHelper.h"
#include "HAL/PlatformFilemanager.h"
#include "GenericPlatform/GenericPlatformFile.h"

void UMyClass::CreateAndWriteFile()
{
    // Define the directory and file paths
    FString DirectoryPath = FPaths::ProjectDir() + TEXT("/MyDirectory");
    FString FilePath = DirectoryPath + TEXT("/hello.txt");

    // Get the platform file manager
    IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();

    // Check if the directory exists
    if (!PlatformFile.DirectoryExists(*DirectoryPath))
    {
        // If the directory doesn't exist, create it
        PlatformFile.CreateDirectory(*DirectoryPath);

        // Check if the directory was created successfully
        if (!PlatformFile.DirectoryExists(*DirectoryPath))
        {
            UE_LOG(LogTemp, Error, TEXT("Failed to create directory!"));
            return;
        }
    }

    // Create the file and write "Hello, World!" to it
    FFileHelper::SaveStringToFile(TEXT("Hello, World!"), *FilePath);

    // Read the file
    FString FileContents;
    FFileHelper::LoadFileToString(FileContents, *FilePath);

    // Print the file's contents to the Unreal Engine log
    UE_LOG(LogTemp, Warning, TEXT("File Contents: %s"), *FileContents);

}
I just pasted it over the little demo test function I was already writing. I'll be damned; it compiled and ran the first time. It looks like the directory gets put under my project directory and all operations were able to use it consistently.

Outside of thinking I need to mention this in the Tech Nightmares thread:
1. Yesterday, I was trying to use FileExists instead of DirectoryExists and that was returning false for a directory I created. So the distinction is really important.
2. I'm trying to understand the significance of the leading slash in the paths. It seems important because when I wasn't using that, I was having some kind of grief with paths. According to the Unreal Source Discord, you need that slash because ProjectDir doesn't have the trailing slash and winds up just concatenating the names instead of paths. I don't know if I'm convinced because when I step debugged, it looked like at least a path concatenation operation tried to run. Now, it could have decided to just glue the text together, but I'd have to restore the old code to be sure.
3. My wife has decided I'm out of a job now and I should just start farming.

more falafel please
Feb 26, 2005

forums poster

1) Yeah, that seems like a semantic difference between the POSIX/unix centric idea of the filesystem and the DOS/Windows idea. While Unreal is pretty good about being cross-platform friendly, its philosophy tends to skew to Windows-focused. In the UE3 days there were DWORDs and BYTEs all over the codebase.

2) I wouldn't be surprised if FPaths::ProjectDir() returns a path-style object (I don't have engine source in front of me at the moment), and i bet concatenating two paths works as expected. Looks like your/ChatGPT's code is concatenating the string literal to the path object, which I'm guessing just does string concat to support something like adding an extension to a filename.

3) Actually writing code is a pretty small part of my job. I'm not confident LLMs will be able to do that consistently well for quite a while, but even if they do: you still, largely speaking, wrote that program. You wrote it in a different language with a buggy transpiler, but you still had to tell it what to do, it just knew the API reference, sorta.

Your Computer
Oct 3, 2008




Grimey Drawer
this is a long shot but i know there's a lot of smart people in here, so here goes:


i'm trying to make a function to multiply two q7.8 fixed point numbers in godot's gdscript (don't ask) and every example i find of this online uses programming languages where you can limit the number of bits of your integers, so for this example this would be something like (int_32)(int_16 a * int_16b) >> 8

however in gdscript all integers are 64-bits and i'm not really sure how to deal with this. my naive implementation was simply (a * b) >> 8 where a and b are both (artificially) limited to 16 bits and this works great for positive numbers whose multiplication never exceed 32 bits but breaks down completely with bigger/negative numbers

for example with this method if i multiply 1.6 by 3.2 i get 5.12 (good) but if i multiply 1.6 by negative 3.5 i get a number larger than 16 bits and chopping it off doesn't work

1.6 * 3.2 is 0x019A * 0x0333 = 0x051F (correct)

1.6 * -3.2 is 0x019A * 0xFCCD = 0x184E0 (incorrect)

the result i'm looking for should be 0xFAE0 so weirdly the fraction is correct but the whole number part is mangled. anyone? :negative:

roomforthetuna
Mar 22, 2005

I don't need to know anything about virii! My CUSTOM PROGRAM keeps me protected! It's not like they'll try to come in through the Internet or something!

Your Computer posted:

i'm trying to make a function to multiply two q7.8 fixed point numbers in godot's gdscript (don't ask) and every example i find of this online uses programming languages where you can limit the number of bits of your integers, so for this example this would be something like (int_32)(int_16 a * int_16b) >> 8
The equivalent of that in any language with non-truncatable values is (assuming & is a bitwise-and operator):
code:
((a&0xffff)*(b&0xffff))>>8
It might need another &0xffff after the multiply before the shift, if the expected behavior is that the result of the multiplication is also truncated to two bytes, which I *think* is what happens if you do that in C++.

Raenir Salazar
Nov 5, 2010

College Slice
Why are you partaking in witchcraft in these most holy of places!? :mad:

Your Computer
Oct 3, 2008




Grimey Drawer
i'm even more confused because it seems like the resolution isn't the problem here? i busted this out on a calculator and indeed i get 194E052 which is a perfectly fine 32-bit number and does contain the EO but not the FA that i expect. so it feels like there has to be some special case to handle negative numbers?

but i can't find anything by googling - heck even the wikipedia article on fixed point arithmetics says "To multiply two fixed-point numbers, it suffices to multiply the two underlying integers" but this clearly isn't producing the right result...!

my only sanity check is the game boy code that i know works, and the multiplication routine i used there is all sorts of unholy wizardry since it does this multiplication on an 8-bit system. but it does produce the right results so i have something to compare to

Raenir Salazar posted:

Why are you partaking in witchcraft in these most holy of places!? :mad:

i'm doing terrible crimes and i walk willingly into hell

Grace Baiting
Jul 20, 2012

Audi famam illius;
Cucurrit quaeque
Tetigit destruens.



not up for real bit twiddling atm, but for negative numbers at least, seems like you could calculate the sign and magnitude of the result separately
Python code:
neg = (a < 0) xor (b < 0)
sign = -1 if neg else 1
mag = abs(a) * abs(b)
return sign * mag
mb?

Raenir Salazar
Nov 5, 2010

College Slice
I do kinda shudder a little at what real world circumstance prompts needing to do bit fiddling, especially in game dev, remember this?

code:
float q_rsqrt(float number)
{
  long i;
  float x2, y;
  const float threehalfs = 1.5F;

  x2 = number * 0.5F;
  y  = number;
  i  = * ( long * ) &y;                       // evil floating point bit level hacking
  i  = 0x5f3759df - ( i >> 1 );               // what the gently caress?
  y  = * ( float * ) &i;
  y  = y * ( threehalfs - ( x2 * y * y ) );   // 1st iteration
  // y  = y * ( threehalfs - ( x2 * y * y ) );   // 2nd iteration, this can be removed

  return y;
}
I think the last time I used bit twiddling was for a poker hand solver.

Your Computer
Oct 3, 2008




Grimey Drawer

Grace Baiting posted:

not up for real bit twiddling atm, but for negative numbers at least, seems like you could calculate the sign and magnitude of the result separately
Python code:
neg = (a < 0) xor (b < 0)
sign = -1 if neg else 1
mag = abs(a) * abs(b)
return sign * mag
mb?
these are two's complement signed integers so i don't think this works right? like all the actual ints involved here are positive numbers in a 64-bit representation, and even in a 16-bit representation they're funky since they're actually representing fixed point numbers

Raenir Salazar posted:

I do kinda shudder a little at what real world circumstance prompts needing to do bit fiddling, especially in game dev
oh there is no logical reason for any of this it's just me doing a little coding crime

i posted about it in the thread previously but i made a lil' pinball collision engine in game boy assembly and as a fun little project i'm working on porting that as closely as possible to godot, i.e. still using 16-bit fixed point integers instead of floats etc. :pram:

ynohtna
Feb 16, 2007

backwoods compatible
Illegal Hen
If you try and take my binary operators away from me, why I'll rotate your bits so hard you'll be XORing trinary for the rest of your shiftless existence.

ynohtna
Feb 16, 2007

backwoods compatible
Illegal Hen
That said, their use should definitely be contained to well-tested, isolated regions of carefully crafted spicy shenanigans.

Raenir Salazar
Nov 5, 2010

College Slice

ynohtna posted:

That said, their use should definitely be contained to well-tested, isolated regions of carefully crafted spicy shenanigans.

Out of curiosity what are some coding crimes you've done with them? It's always amazing what sort of really spicy performant code that comes out of stuff.

Your Computer
Oct 3, 2008




Grimey Drawer
yeah okay i decided i needed someone way smarter than me to look at this and the only place i know of with experts on this sort of thing is the game boy dev discord server. i didn't wanna just barge in there and ask about a non-game boy related thing but i was desperate

within minutes someone helps me solve it
code:
func mul_q78(a : int, b : int) -> int:
	var out : int = ((a << 48) >> 48) * ((b << 48) >> 48)
	return (out >> 8) & 0xFFFF
i already knew (as i stated) that godot uses 64-bit signed integers, and apparently with this knowledge you can simply shift the operands left by 48 bits then back again and this creates world peace solves everything. it's called "sign extension" and as far as i can understand it works by essentially "extending" the sign bit of our 16-bit number to read as a 64-bit signed number which then makes the multiplication work as expected. like you're just sliding that sign bit into place and then going back. there is ZERO way i would've solved this on my own

for good measure they also gave me another way to solve it like so
code:
func another_mul_q78(a: int, b: int) -> int:
	var out = a * b
	if a & 0x8000:
		out = out - (b << 16)
	if b & 0x8000:
		out = out - (a << 16)
	return (out >> 8) & 0xFFFF

roomforthetuna
Mar 22, 2005

I don't need to know anything about virii! My CUSTOM PROGRAM keeps me protected! It's not like they'll try to come in through the Internet or something!
Oh, right, sorry, I missed that it was signed int16s.

ynohtna
Feb 16, 2007

backwoods compatible
Illegal Hen

Raenir Salazar posted:

Out of curiosity what are some coding crimes you've done with them? It's always amazing what sort of really spicy performant code that comes out of stuff.

I swear, as an old from the time of bit-plane video (pain!) and floating point being an additional processor, who has done far more low-level MIDI and audio work than is sensible, that I have only ever followed orders and the rules of engagement of the time.

Tagging aligned pointers by using the unused bits is a trick that never gets old (but that detail must never ever leak beyond an implementation barrier). As does packing data as tight as possible into platform size words so atomic compare-and-swap can be used for inter-thread communication instead of more complicated mechanisms. Floating point tricks for clamping, square roots, saturation or fixing denormalized numbers come out of the toolbox occasionally.

I'm easily dazzled by blinky things, though, and get a simple kick out of XOR. I'm literally coding up a munching squares variant ((x ^ y) < t) to trigger dumb synthesizer stuff this weekend! :awesomelon:

KillHour
Oct 28, 2007


ynohtna posted:

munching squares variant ((x ^ y) < t)

I'll be stealing this tyvm

OneEightHundred
Feb 28, 2008

Soon, we will be unstoppable!

Your Computer posted:

for good measure they also gave me another way to solve it like so
code:
func another_mul_q78(a: int, b: int) -> int:
	var out = a * b
	if a & 0x8000:
		out = out - (b << 16)
	if b & 0x8000:
		out = out - (a << 16)
	return (out >> 8) & 0xFFFF
Technically shifting things into the sign bit is undefined behavior so certain compilers might complain about it or do weird things.

The most reliable way to do sign extension of a 16-bit number (assuming it's already in a signed type larger than 16 bits) is just
code:
result = ((n ^ 0x8000) - 0x8000)

OneEightHundred fucked around with this message at 21:06 on Feb 4, 2024

Kaedric
Sep 5, 2000

Raenir Salazar posted:

Out of curiosity what are some coding crimes you've done with them? It's always amazing what sort of really spicy performant code that comes out of stuff.

Not really performant, but: I used to always use bit fiddling for annoying boolean logic checks (for MUDs/rpgs). So you could flag someone as having finished certain checkpoints by giving them some integer PLOT variable, and having a series of values of powers of 2 for each thing they need to check off the list. So instead of having a huge block of if(finished_part_1 && finished_part_2 && finished_part_3_good_ending && && && && && etc) you could just make a variable ALL_CHECKPOINTS_GOOD_OUTCOME (which would be like 10001110101110011, each '1' here being a flag that you set when they did the thing), and you just compare it to their PLOT var. If they have the bits in the right places then they did the thing and you can proceed.

It's basic stuff but back in the 90s that blew my mind.

Raenir Salazar
Nov 5, 2010

College Slice

Kaedric posted:

Not really performant, but: I used to always use bit fiddling for annoying boolean logic checks (for MUDs/rpgs). So you could flag someone as having finished certain checkpoints by giving them some integer PLOT variable, and having a series of values of powers of 2 for each thing they need to check off the list. So instead of having a huge block of if(finished_part_1 && finished_part_2 && finished_part_3_good_ending && && && && && etc) you could just make a variable ALL_CHECKPOINTS_GOOD_OUTCOME (which would be like 10001110101110011, each '1' here being a flag that you set when they did the thing), and you just compare it to their PLOT var. If they have the bits in the right places then they did the thing and you can proceed.

It's basic stuff but back in the 90s that blew my mind.

That's very interesting, I haven't yet really had a chance yet myself trying to tackle that problem of how to track story progression and that's a very interesting one. But how scalable is that? I assume you're limited by the total number of bits? You could probably use an arbitrarily sized array and depending on the language maybe you can do a similar check. It doesn't seem super readable to me though, I wonder what the other solutions for that people have come up with.

leper khan
Dec 28, 2010
Honest to god thinks Half Life 2 is a bad game. But at least he likes Monster Hunter.

Raenir Salazar posted:

That's very interesting, I haven't yet really had a chance yet myself trying to tackle that problem of how to track story progression and that's a very interesting one. But how scalable is that? I assume you're limited by the total number of bits? You could probably use an arbitrarily sized array and depending on the language maybe you can do a similar check. It doesn't seem super readable to me though, I wonder what the other solutions for that people have come up with.

for linear progression, you need log bits. which is why metroid prime only uses one save block

packed structs arent that hard to read or work with, really.

Raenir Salazar
Nov 5, 2010

College Slice

leper khan posted:

for linear progression, you need log bits. which is why metroid prime only uses one save block

packed structs arent that hard to read or work with, really.

I mean more from a broader architectural perspective, i.e patterns like for an RPG. For questions like, who is responsible for keeping this information, how to pass it around and access it in a "Clean" way, maybe I wanna do something a bit complication and OOP based where maybe I maintain "Progression" differently for different aspects of the game, maybe the system that tracks what a NPC remembers you doing/saying is different from the system that tracks if you reached a certain area and is different from the how the overall story is saved/progressed (i.e through some sort of graph/node network) and then there needs to be system or manager that manages and keeps track of all this while keeping/maintaining enough like commonality for easy mocking and unit testing etc so adding a new type of quest doesn't brick everything else without warning you first.

bobthenameless
Jun 20, 2005

i think everyone's more or less on the same page here but:

you'd defininitely want to apply it tactfully but i think some basic twiddling is still pretty applicable in a lot of cases (and also not a lot of cases) and still worth considering for some games today

things like older FF games might have for example a buff bitfield thats however many bits you need for blind/petrified/etc and each character/monster has its own copy of that bitfield, and also for characters it'd be saved so you can come back later and walk around with poison slowly killing your party. or for tracking which megaman bosses you killed, you just need the 8 dead or not bits.

for a more complicated buff system this may be too simple on its own but you can build a system of timers etc around it, or have a battle system know how to deal with actors that have the field, or just use a different data structure entirely really. i guess more often nowadays you'd just throw a BuffComponent on an entity

in c# ime with its flag enums its not the worst for reading/later understanding purposes and gives a bit more of an interface as well:

C# code:
[Flags] enum Buff {
  None = 0,
  Strength = 1,
  Dexterity = 2, // 1 << 1
  Bless = 4, // 1 << 2
}
[Flags] enum Debuff {
  None = 0,
  Poison = 1
}

//on whatever class or object
class Monster : Actor {	
	Buff buffs;
	Debuff debuffs;

	void TogglePoisonStatus() {
	  self.debuffs ^= Debuff.Poison;
	}
	
	void UnsetBlessStatus() {
	  self.buffs &= ~Buff.Bless;
	}
	
	void SetSuperStatus() {
	  self.buffs |= Buff.Strength;
	  self.buffs |= Buff.Dexterity;
	 //self.buffs = self.buffs | Buff.Strength | Buff.Dexterity;
	} 
}


////
void Tick() {
	var mob = GetMob();
        if (mob.debuffs.HasFlag(Debuff.Poisoned)) {
		//
	}
}
can useful be for optimizing de/serialization in general too - a struct of 8 bools vs a uint8 not a huge deal for one thing but can add up if youre doing it for thousands of objects per minecraft chunk say or constantly sending packets of it to 1000s of players or as part of a common event payload etc. making a lot of assumptions for word size etc, the difference between 8 bits and 512 bits (8 64-bit bools) can be nothing to worry about or incredibly wasteful

this follows standard programming procedure for optimizing things and what youre comfortable with, and it is also more trouble than its worth for a lot of things ityool 2024 than it was in 87 when it was necessary when developing metroid. (which btw, if you want to see how they tracked the whole game in 128 bits for its password system this has some info)

also you can run into them a lot in some interfaces and sometimes you just gotta deal with it, a simple example being windows modifer keys . its just a solid and classic way to do this kind of thing too even if you dont have reasons to do advanced twiddling and easily dealt with in whatever environment

i think what im trying to get at writing all this is that some bit twiddling can still be handy to have for critical data and still have managers, even a light one like the C# flags, around these bits if you want better maintainability and still be able to get to the bits if you do need them; its also a wildly available option across every (most?) languages/frameworks/paradigm/platform and not always just an ambitious optimization or arcane ritual

in summary i like bit flips and i cannot lie u other coders cant deny

bobthenameless fucked around with this message at 08:10 on Feb 18, 2024

leper khan
Dec 28, 2010
Honest to god thinks Half Life 2 is a bad game. But at least he likes Monster Hunter.
you dont need to mark explicit numbers in a flags enum [except for on None]

bobthenameless
Jun 20, 2005

sure, also probably worth noting that HasFlag used to be much worse for performance in the dotnet framework days but is much faster now and closer to a bitwise op since Core 3 at least.

KillHour
Oct 28, 2007


I'm cross posting because I'm still mad about it and I want to complain again

KillHour posted:

:v:: "I finally finished implementing enough of a DX12 pipeline to start testing"

:ins:: D3D12 ERROR: ID3D12Device::CreatePixelShader: Shader uses interfaces, which are not supported by D3D12.

:v:: "YOU loving gently caress WHY THE gently caress DOES SHADER MODEL 5.1 EVEN SUPPORT IT THEN WHAT THE FUUUUUUUUUUUUUUUU"

Red Mike
Jul 11, 2011

leper khan posted:

you dont need to mark explicit numbers in a flags enum [except for on None]

Yes, you do, otherwise you'll get the wrong behaviour when using them as flags.

code:
public enum TestEnum
{
	A,
	B,
	C,
	D
}

[Flags]
public enum TestFlagsEnum
{
	A,
	B,
	C,
	D
}

[Flags]
public enum ProperFlagsEnum
{
	A = 0b0000001,
	B = 0b0000010,
	C = 0b0000100,
	D = 0b0001000
}

foreach (var val in Enum.GetValues<TestEnum>())
{
	Console.WriteLine($"TestEnum.{val:G} {val:D}");	
}

foreach (var val in Enum.GetValues<TestFlagsEnum>())
{
	Console.WriteLine($"TestFlagsEnum.{val:G} {val:D}");	
}

var isBAndC = TestFlagsEnum.B | TestFlagsEnum.C;
var isD = TestFlagsEnum.D;
Console.WriteLine($"These two should not be equal: {isBAndC} != {isD}");

foreach (var val in Enum.GetValues<ProperFlagsEnum>())
{
	Console.WriteLine($"ProperFlagsEnum.{val:G} {val:D}");	
}
		
var isBAndC2 = ProperFlagsEnum.B | ProperFlagsEnum.C;
var isD2 = ProperFlagsEnum.D;
Console.WriteLine($"These two should not be equal: {isBAndC2} != {isD2}");
code:
TestEnum.A 0
TestEnum.B 1
TestEnum.C 2
TestEnum.D 3
TestFlagsEnum.A 0
TestFlagsEnum.B 1
TestFlagsEnum.C 2
TestFlagsEnum.D 3
These two should not be equal: D != D
ProperFlagsEnum.A 1
ProperFlagsEnum.B 2
ProperFlagsEnum.C 4
ProperFlagsEnum.D 8
These two should not be equal: B, C != D
e: also in general it saves so much sanity to just always specify explicit values for enums, because you avoid running into issues where another programmer/new dev/etc adds a value to the middle of your enum and suddenly all your previously serialised values change meaning (or your client can't understand the server any more and vice versa).

Red Mike fucked around with this message at 21:13 on Feb 18, 2024

Ranzear
Jul 25, 2013

Kaedric posted:

Not really performant, but: I used to always use bit fiddling for annoying boolean logic checks (for MUDs/rpgs). So you could flag someone as having finished certain checkpoints by giving them some integer PLOT variable, and having a series of values of powers of 2 for each thing they need to check off the list. So instead of having a huge block of if(finished_part_1 && finished_part_2 && finished_part_3_good_ending && && && && && etc) you could just make a variable ALL_CHECKPOINTS_GOOD_OUTCOME (which would be like 10001110101110011, each '1' here being a flag that you set when they did the thing), and you just compare it to their PLOT var. If they have the bits in the right places then they did the thing and you can proceed.

It's basic stuff but back in the 90s that blew my mind.

With the context of crimes: I did hundreds of these booleans, labeled it as ascii, and dumped them in a blob column in the database.

It worked perfectly because their suggested alternative was having hundreds of columns, with even more story flags being made willy-nilly.

leper khan
Dec 28, 2010
Honest to god thinks Half Life 2 is a bad game. But at least he likes Monster Hunter.

Red Mike posted:

Yes, you do, otherwise you'll get the wrong behaviour when using them as flags.

code:
public enum TestEnum
{
	A,
	B,
	C,
	D
}

[Flags]
public enum TestFlagsEnum
{
	A,
	B,
	C,
	D
}

[Flags]
public enum ProperFlagsEnum
{
	A = 0b0000001,
	B = 0b0000010,
	C = 0b0000100,
	D = 0b0001000
}

foreach (var val in Enum.GetValues<TestEnum>())
{
	Console.WriteLine($"TestEnum.{val:G} {val:D}");	
}

foreach (var val in Enum.GetValues<TestFlagsEnum>())
{
	Console.WriteLine($"TestFlagsEnum.{val:G} {val:D}");	
}

var isBAndC = TestFlagsEnum.B | TestFlagsEnum.C;
var isD = TestFlagsEnum.D;
Console.WriteLine($"These two should not be equal: {isBAndC} != {isD}");

foreach (var val in Enum.GetValues<ProperFlagsEnum>())
{
	Console.WriteLine($"ProperFlagsEnum.{val:G} {val:D}");	
}
		
var isBAndC2 = ProperFlagsEnum.B | ProperFlagsEnum.C;
var isD2 = ProperFlagsEnum.D;
Console.WriteLine($"These two should not be equal: {isBAndC2} != {isD2}");
code:
TestEnum.A 0
TestEnum.B 1
TestEnum.C 2
TestEnum.D 3
TestFlagsEnum.A 0
TestFlagsEnum.B 1
TestFlagsEnum.C 2
TestFlagsEnum.D 3
These two should not be equal: D != D
ProperFlagsEnum.A 1
ProperFlagsEnum.B 2
ProperFlagsEnum.C 4
ProperFlagsEnum.D 8
These two should not be equal: B, C != D
e: also in general it saves so much sanity to just always specify explicit values for enums, because you avoid running into issues where another programmer/new dev/etc adds a value to the middle of your enum and suddenly all your previously serialised values change meaning (or your client can't understand the server any more and vice versa).

what a garbage language why

Red Mike
Jul 11, 2011
Because all [Flags] does is change the ToString method on the enum (or rather the ToString knows to check for it), and enable a couple code inspection warnings in a compatible IDE. You could use normal enums and have them act the same as a flags one, but you'd end up with confusing ToString behaviour when you combine flags (unless the combined value maps to an existing name).

It gets more fun when you realise that you can have multiple names that point to the same value (see HttpStatusCode that has Moved = 301 and MovedPermanently = 301), and that evaluating equality between them gets confusing quickly.

There's a reason I said it just saves so much sanity to keep enums simple, explicit, and expressive, and not rely on any built-in handling.

StashAugustine
Mar 24, 2013

Do not trust in hope- it will betray you! Only faith and hatred sustain.

I've been tooling around with starting a loose board game adaptation using Godot, but past the opening stuff (taking some hex based code and adapting it to move pieces around) I'm a little intimidated by how to organize a turn based game with Godot. Does anyone have recommendations on how to structure that kind of program?

leper khan
Dec 28, 2010
Honest to god thinks Half Life 2 is a bad game. But at least he likes Monster Hunter.

StashAugustine posted:

I've been tooling around with starting a loose board game adaptation using Godot, but past the opening stuff (taking some hex based code and adapting it to move pieces around) I'm a little intimidated by how to organize a turn based game with Godot. Does anyone have recommendations on how to structure that kind of program?

sure. build the program independent of the presentation layer. then have the presentation read and modify your state through its API

TooMuchAbstraction
Oct 14, 2012

I spent four years making
Waves of Steel
Hell yes I'm going to turn my avatar into an ad for it.
Fun Shoe
Yeah, the underlying game will have a state, and a set of valid actions (functions) which modify that state. Those actions correspond to moves/turns that players can make. The game should update to the new state instantly in response to the functions being invoked. The display layer is responsible for turning the user's input into function calls, and for updating the displayed game state to match the underlying state.

Kaedric
Sep 5, 2000

Raenir Salazar posted:

That's very interesting, I haven't yet really had a chance yet myself trying to tackle that problem of how to track story progression and that's a very interesting one. But how scalable is that? I assume you're limited by the total number of bits? You could probably use an arbitrarily sized array and depending on the language maybe you can do a similar check. It doesn't seem super readable to me though, I wonder what the other solutions for that people have come up with.

You are limited by bits, but that just takes organization (by splitting up conceptual flags into different variables).

I will agree that the idea is not inherently easy to understand, but I will argue that it is definitely readable in comparison to managing flags/state via booleans. There are probably several clever ways to deal with flags/state out there but I don't know them, so I'll just talk what I know :)

Let me show a brief example of a naive boolean flag management. Whether these variables are tucked away in an array/struct/whatever is just set dressing, you're still accessing them directly to discover their value:

code:
#.... somewhere declared:
bool KILLED_MEDUSA
bool SAVED_PRINCESS
bool KICKED_DRAGON_IN_GROIN
bool DIDNT_STEAL_LOLLIPOP_FROM_CHILD
...
#... somewhere used.  Works just fine for individual checks, but gets uglier the more compound/related checks you have.  You can see that even with just a few checks it starts to get annoying, now imagine you might be juggling 20+ flags
if(player.KILLED_MEDUSA) { give_reward(500) }
...
if(player.SAVED_PRINCESS){
    if(player.KICKED_DRAGON_IN_GROIN){
        if(player.DIDNT_STEAL_LOLLIPOP_FROM_CHILD){ give_reward(1000) }
        else { give_reward(500) }
    }
    ... other checks
}
...
#...alternative way of writing the above:
if(player.SAVED_PRINCESS && player.KICKED_DRAGON_IN_GROIN && player.DIDNT_STEAL_LOLLIPOP_FROM_CHILD) { give_reward(1000) }
if(player.SAVED_PRINCESS && player.KICKED_DRAGON_IN_GROIN && !player.DIDNT_STEAL_LOLLIPOP_FROM_CHILD) { give_reward(500) }
Again, there's better ways to do it but I'm doing a contrived example here, get off my back!
Something similar but with bitfiddle (don't @ me about using int, you can use whatever, some languages have more options here):

code:
#.... somewhere declared:
int KILLED_MEDUSA 			= 1 // visualize as 00000001
int SAVED_PRINCESS 			= 2 // visualize as 00000010, the same as 1<<1
int KICKED_DRAGON_IN_GROIN 		= 4 // visualize as 00000100, the same as 1<<2
int DIDNT_STEAL_LOLLIPOP_FROM_CHILD 	= 8 // visualize as 00001000, the same as 1<<3, every flag we make is a power of two, even the first flag(2 to 0 = 1)
...
#...Shortcut flags, used for easier comparison
int PRINCESS_GOOD_ENDING = SAVED_PRINCESS + KICKED_DRAGON_IN_GROIN + DIDNT_STEAL_LOLLIPOP_FROM_CHILD // same as just saying PRINCESS_GOOD_ENDING = 14, or 00001110 but more readable imo
int PRINCESS_BAD_ENDING = SAVED_PRINCESS + KICKED_DRAGON_IN_GROIN
...
#..Whenever player does a thing, flip a bit on their plot var, (i.e. player.plot_flags = player.plot_flags | KICKED_DRAGON_IN_GROIN)
if(player.plot_flags & PRINCESS_GOOD_ENDING == PRINCESS_GOOD_ENDING) { give_reward(1000) }
else if(player.plot_flags & PRINCESS_BAD_ENDING == PRINCESS_BAD_ENDING) { give_reward(500) }
And now that I've spent the time to write all that useless fluff above I realize that because it's a contrived example, it's sort of a bad one. Thinking back on it, you'd probably use this mostly for checking things that you check OFTEN, not necessarily something like a quest reward which is a one and done. Maybe a better example would be combining elemental effects to create a spell. Instead of checking has_earth && has_fire && has_wind you might make a shorthand bit checker for that. I don't know! I'm tired.

Adbot
ADBOT LOVES YOU

12 rats tied together
Sep 7, 2006

an example of this i've worked with in the past is skills for an mmorpg. this skill "can target the ground" and "has an area of effect" and "is a trap" and "ignores defense", and so on.

it's easy to rack up dozens of these and cramming them into a single "battle flags" integer and unpacking it is pretty ergonomic as far as mmorpg code goes

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply