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
seiken
Feb 7, 2005

hah ha ha

nelson posted:

I’ve heard good things about bazel from one guy at our company but I’ve never used it myself. Have any of you used it and if so what is your opinion?

I've used it for a long time now.

It has a fairly steep learning curve, it's very opinionated, and yeah it's no small feat to migrate an existing nontrivial codebase to it.

Dependencies are also not always easy (though it's getting better). rules_foreign_cc allows you to have bazel invoke cmake to build dependencies, but I haven't used it and can't say how well it works. There's also the new bazel central registry, which does let you grab some things with one line, but there's not a ton of stuff there yet. I usually end up writing small custom build files for most dependencies.

If you can deal with all that, you get a hugely powerful, sane, multi-language build system that just works and does the right thing every time. I sometimes run bazel clean just to check everything works from scratch and because I get a strange sense of satisfaction from seeing nice build system outputs churning away, but I can't remember the last time it was actually necessary to fix something. Personally, I wouldn't go back.

seiken fucked around with this message at 12:18 on May 11, 2024

Adbot
ADBOT LOVES YOU

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!
I prefer old-school Makefiles for having full control over everything, but if I had to use a modern thing, I like bazel a lot more than CMake.

The learning curve to get something just generally working isn't too bad, but once you start getting into needing to be able to import a dependency that isn't something you own, yeah, it can get pretty gnarly. That's probably true of any system.

Subjunctive
Sep 12, 2006

✨sparkle and shine✨

and bazel’s ability to do a shared artifact cache can be really nice once team and program get bigger. CI tends to keep that cache very warm, so you pretty much only have to compile locally modified stuff ever even after a big pull

pokeyman
Nov 26, 2006

That elephant ate my entire platoon.
I remember more about the couple hours I messed with bazel than the 20 hours I've messed with cmake. Something about cmake is just perfectly designed to slip right off my brain. Makes zero sense to me.

Anyway op cmake is the safe choice. If you try something weird: redo.

Nalin
Sep 29, 2007

Hair Elf
its not a proper c++ build system if you're not doing something weird like using premake5. the joy in life is reverse engineering the build system so you can bolt it on to your own mess

csammis
Aug 26, 2003

Mental Institution
We use waf. It's fine. We have a build tools team to babysit it and I don't have to spend more than a normal amount of mental bandwidth trying to unfuck the tools we depend on for our livelihoods.

giogadi
Oct 27, 2009

In my game engine, I have lots of code for structs that serializes, deserializes, and renders ImGui like the following:

code:
struct FooEntity {
	int hp;
	float maxSpeed;

	void Save(XMLNode* xml) const {
		xml->WriteInt("hp", hp);
		xml->WriteFloat("maxSpeed", maxSpeed);
	}
	void Load(XMLNode const* xml) {
		hp = xml->ReadInt("hp");
		maxSpeed = xml->ReadFloat("maxSpeed");
	}
	void ImGui() {
		ImGui::InputInt("hp", &hp);
		ImGui::InputFloat("maxSpeed", &maxSpeed);
	}
};
However, I'm adding some functionality to diff two entities of the same type, which requires looping over each of these serializable "properties". So I need a way to loop over these members of a struct. I don't have reflection in C++, so I wrote this code that represents Properties as collections of Load/Save/ImGui functions; now I can implement Save/Load/ImGui just one time and it will work for any struct with a corresponding list of Properties:

code:
typedef void (*PropertyLoadFn)(void* v, XMLNode const* xml);
typedef void (*PropertySaveFn)(void const* v, XMLNode* xml);
typedef bool (*PropertyImGuiFn)(char const* name, void* v);
struct PropertySpec {
	PropertyLoadFn loadFn;
	PropertySaveFn saveFn;
	PropertyImGuiFn imguiFn; 
};

void PropertyLoadInt(void* v, XMLNode const* xml) { /* snip */ }
void PropertySaveInt(void const* v, XMLNode* xml) { /* snip */ }
void PropertyImGuiInt(char const* name, void* v) { /* snip */ }

PropertySpec const gPropertySpecInt {
	.loadFn = &PropertyLoadInt,
	.saveFn = &PropertySaveInt,
	.imguiFn = &PropertyImGuiInt
};

/* repeat above for gPropertySpecFloat */

struct Property {
    char const* name;
    PropertySpec* spec; 
    size_t offset;
};

void SaveObject(XMLNode* xml, Property const* properties, int numProperties, void* object) {
	// Go through each property, use Property::offset to access the corresponding member of object, and call its PropertySpec::saveFn
}
void LoadObject(/*snip*/)
void ImGuiObject(/*snip*/)
...and finally, this is all it would take to add Save/Load/ImGui to FooEntity:

code:
struct FooEntity {
	int hp;
	float maxSpeed;
};

Property gFooEntityProperties[] = {
	{
		.name = "hp",
		.spec = &gPropertySpecInt,
		.offset = offsetof(FooEntity, hp)
	},
	{
		.name = "maxSpeed",
		.spec = "&gPropertySpecFloat,
		.offset = offsetof(FooEntity, maxSpeed);
	}
}

I was very proud of this! And this actually works very well for simple C structs. However, when I tried adding it to my engine where some of my classes are derived virtual classes, clang gave me warnings that offsetof should not be used for "non standard layouts"!!!

Is there a way to do what I'm trying above that works safely for classes with vtables? For example, if I replace "offsetof()" with C++ member pointers?

korora
Sep 3, 2011
I think you’re going to hit a dead end with that array of structs because you are going to want heterogeneous types (e.g. member pointers are not uniform types). A solution I have used to reduce boilerplate is to add a function like this to your serialized types:
code:
template <typename S>
void serialize(S serializer) {
    serializer(“hp”, hp, “maxSpeed”, maxSpeed);
}
Then you can write functors to pass into that function that implement your XML save/load and ImGui hooks.

You also shouldn’t need the function pointers for float/int - define those as overloads and let the compiler do the work for you.

giogadi
Oct 27, 2009

(Ugh, I accidentally hit a keyboard shortcut that just posted my message before it was done. Deleting this post)

(LMAO it happened again below. Apparently I was accidentally hitting Cmd+Enter and that just auto-posts whatever you have)

giogadi fucked around with this message at 16:16 on May 14, 2024

giogadi
Oct 27, 2009

I've seen the template-based pattern you mention, but boilerplate isn't the only problem I'm trying to solve - I'm trying to make it easy to write a for-loop over all my properties (for diffing), which is why I'm going with the array-of-property-structs thing. (I'm aware that you can use template metaprogramming with a kinda recursion-like pattern to do "loops" in templates, but I really don't want to go that route)

I was hoping I could get around the heterogeneous type issue by just casting the member pointer into a pure size_t offset; so for example:

code:
struct Property {
    char const* name;
    PropertySpec* spec; 
    size_t offset;
};

struct FooEntity {
        virtual SomeVirtualFunction();

	int hp;
	float maxSpeed;
};

Property gFooEntityProperties[] = {
	{
		.name = "hp",
		.spec = &gPropertySpecInt,
		.offset = (size_t) &FooEntity::hp
	},
	{
		.name = "maxSpeed",
		.spec = &gPropertySpecFloat,
		.offset = (size_t) &FooEntity::maxSpeed
	}
}

// Then, you can access "hp" through a void* by doing:
FooEntity foo;
void* object = &foo;
Property* hpProperty = gFooEntityProperties[0];
int* hp = (int*) ((char*)object + hpProperty->offset);
My question is whether the above is "safe" - I don't see why it wouldn't be, but maybe there are things going on under-the-hood of classes with virtual functions that I don't understand.

giogadi fucked around with this message at 16:15 on May 14, 2024

giogadi
Oct 27, 2009

korora posted:

code:
template <typename S>
void serialize(S serializer) {
    serializer(“hp”, hp, “maxSpeed”, maxSpeed);
}

Actually, one question about the above pattern: for the functors you pass into this, do you require the functors to be variadic? I can't see how else this would work...

korora
Sep 3, 2011

giogadi posted:

Actually, one question about the above pattern: for the functors you pass into this, do you require the functors to be variadic? I can't see how else this would work...

Yes, but if you don’t want to implement a variadic template operator() in your functor you could split it into N calls to the functor instead.

Not totally clear on the diffing use case but perhaps that is also supported by writing a different functor?

rjmccall
Sep 7, 2007

no worries friend
Fun Shoe
If you’re generating code anyway, just generate the obvious code instead of tying yourself into knots with multiple levels of abstraction.

giogadi
Oct 27, 2009

rjmccall posted:

If you’re generating code anyway, just generate the obvious code instead of tying yourself into knots with multiple levels of abstraction.

I've tried the codegen route as well. It does work but it's exactly annoying enough that I get lazy about using it. Maybe my head is really far up my rear end already but I don't find the above scheme confusing at all; I just want to know whether it's safe to assume that casting a pointer-to-member-variable to a size_t is a safe thing to depend on.

edit: in case it's not clear, I'm hoping to _not_ generate code here. Adding the array of Property structs at the end of a struct declaration would be a manual process.

giogadi fucked around with this message at 16:45 on May 14, 2024

OddObserver
Apr 3, 2009

giogadi posted:

I've tried the codegen route as well. It does work but it's exactly annoying enough that I get lazy about using it. Maybe my head is really far up my rear end already but I don't find the above scheme confusing at all; I just want to know whether it's safe to assume that casting a pointer-to-member-variable to a size_t is a safe thing to depend on.



Pretty sure it won't build, but it's been ages since I used those. I would just have:
code:
class Serializable {
 virtual void Save(blah)
  virtual void Load(blah)
};

template<typename T> class Prop: public Serializable {}
struct FooEntity {
 Prop<int> hp;
 Prop<float> whatever;
};

...Though this wastes memory.


[/code]

giogadi
Oct 27, 2009

Thanks for the suggestions, y'all!

I think the main use case for this weird poo poo I'm doing still isn't clear, so let me try to explain:

In my game editor, I'd like to be able to select multiple entities of the same type and then edit all their properties simultaneously. Ideally, I would avoid implementing this multi-edit functionality separately for each individual entity type. The most straightforward way I could think of to do this was for each serializable struct to have a literal array of Property structs so I could implement the multi-edit functionality like this:

code:
void MultiEditImGui(Property* properties, size_t numProperties, void** entities, size_t numEntities) {
    for (int propIx = 0; propIx < numProperties; ++propIx) {
        for (int entityIx; entityIx < numEntities; ++entityIx) {
            // Interesting multi-edit logic that includes diffing and applying
            // one ImGui element's result to all the entities
        }
    }
}
So my interest is less about avoiding boilerplate and more about being able to reason about a struct's serializable fields as an array to be looped over for the above use case.

rjmccall
Sep 7, 2007

no worries friend
Fun Shoe
If you’re willing to write unportable code that technically has UB, sure, go at it.

giogadi
Oct 27, 2009

rjmccall posted:

If you’re willing to write unportable code that technically has UB, sure, go at it.

Ok, so casting a pointer-to-member as a size_t is unportable and UB? That's a helpful response, thank you.

I've been meaning to rewrite my entity system to use only C-style simple structs, and this is just another reason to throw on the pile.

rjmccall
Sep 7, 2007

no worries friend
Fun Shoe
Yeah, there’s no supported way to turn a member pointer into an offset. Storing it into memory and reinterpreting is mostly portable in practice as long as you aren’t tripping one of the cases where MSVC uses wider member pointers, which IIRC requires virtual bases or incomplete types. But that kind of bitwise punning is pretty much always going to be UB one way or the other.

giogadi
Oct 27, 2009

Just in case anyone's curious, I sketched out a terrible thing using templates that does the multi-ImGui functionality I'm looking for (inspired by korora's suggestion). The main downsides of this approach are:

(1) :barf:

(2) It requires knowing at compile-time what type of entities are in the list. However, in general I would have a list of entities of various types, which I would check at runtime for whether they are all the same and then use the multi-ImGui functionality if so. I can still make this work by having a big switch statement on the runtime-determined entity type that then dispatches to the appropriate type's Serialize() function.

I don't think I'll actually use this, but it was educational to write it out.

code:
// Used to get the underlying type of a pointer-to-member
template <class C, typename T>
T getPointerType(T C::*v);

// returns true if it was changed
bool ImGui(char const* name, int* v) { /*snip*/ }
bool ImGui(char const* name, float* v) { /*snip*/ }

template<typename EntityType>
struct MultiImGuiFunctor {
    EntityType* entities;
    size_t count;

    // For each (name, pointer-to-member) pair, we do one ImGui() element
    // with a value seeded on the first entity's value, and if that element
    // actually gets changed, we propagate that change to all the entities.
    template<typename PointerToMemberType, typename... TailTypes>
    void operator()(char const* name, PointerToMemberType pMember, TailTypes... tails) {
        decltype(getPointerType(pMember)) value;
        EntityType& firstEntity = entities[0];
        value = firstEntity.*pMember;
        bool changed = ImGui(name, &value);
        if (changed) {
            for (int entityIx = 0; entityIx < count; ++entityIx) {
                EntityType& e = entities[entityIx];
                e.*pMember = value;
            }
        }
        this->operator()(tails...);
    }

    void operator()() {
        return;
    }
};

struct FooEntity {
    int x;
    float y;
};

template<typename Serializer>
void FooSerialize(Serializer s) {
    s("x", &FooEntity::x, "y", &FooEntity::y);
}

int main() {
    FooEntity entities[10];
    MultiImGuiFunctor<FooEntity> multi;
    multi.entities = entities;
    multi.count = 10;
    FooSerialize(multi);
    
    return 0;
}

giogadi
Oct 27, 2009

rjmccall posted:

Yeah, there’s no supported way to turn a member pointer into an offset. Storing it into memory and reinterpreting is mostly portable in practice as long as you aren’t tripping one of the cases where MSVC uses wider member pointers, which IIRC requires virtual bases or incomplete types. But that kind of bitwise punning is pretty much always going to be UB one way or the other.

Thanks! If I only use simple C structs, is it guaranteed to be safe to access members through offsetof() like below?

code:
struct Foo {
    int x;
    float y;
};

Foo foo;
void* pFoo = &foo;
float* value = (float*)((char*)pFoo + offsetof(Foo, y));
*value = 1.0f;

giogadi fucked around with this message at 19:01 on May 14, 2024

Adbot
ADBOT LOVES YOU

rjmccall
Sep 7, 2007

no worries friend
Fun Shoe
Yeah, absolutely, as long as the access is the right type for the field.

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