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
chglcu
May 17, 2007

I'm so bored with the USA.

Raenir Salazar posted:

I think they are, making it an array instead actually makes it about 2.2s faster. Largely negating the time of the second pass. :)

Makes it around 5s exactly now as the total runtime. So subtract 2.2s and we're down to 2.8s for the first pass.

Cool. Still looking for other potential problems, but in the meantime one thing you could try is pulling the results of some of the repeated calculations into temporary variables. Theoretically the compiler could be doing this for you, but I'm not sure how aggressive the C# compiler is about it. I'm talking about the things like the repeated ocurences of stuff like "y * maxWidth + x". It may or may not have any effect at all.

e: y * maxWidth, for example could be pulled out of the inner loop entirely and only be recalculated when the value y changes.

chglcu fucked around with this message at 05:06 on Oct 28, 2021

Adbot
ADBOT LOVES YOU

Raenir Salazar
Nov 5, 2010

College Slice

chglcu posted:

Cool. Still looking for other potential problems, but in the meantime one thing you could try is pulling the results of some of the repeated calculations into temporary variables. Theoretically the compiler could be doing this for you, but I'm not sure how aggressive the C# compiler is about it. I'm talking about the things like the repeated ocurences of stuff like "y * maxWidth + x". It may or may not have any effect at all.

e: y * maxWidth, for example could be pulled out of the inner loop entirely and only be recalculated when the value y changes.

That's an idea, I'll take a look.

Another possibility might be the UnionFind; there's a different out out there here but I'm not sure how it is supposed to be used in practice. Is it like Label where I just instantiate a whole bunch of these or is it just supposed to be the one? Does Count() return the Root if the former?

chglcu
May 17, 2007

I'm so bored with the USA.

Raenir Salazar posted:

That's an idea, I'll take a look.

Another possibility might be the UnionFind; there's a different out out there here but I'm not sure how it is supposed to be used in practice. Is it like Label where I just instantiate a whole bunch of these or is it just supposed to be the one? Does Count() return the Root if the former?

I'll have to read the article a bit more in depth to answer that one. I'm not previously familiar with the algorithm.

Raenir Salazar
Nov 5, 2010

College Slice

chglcu posted:

I'll have to read the article a bit more in depth to answer that one. I'm not previously familiar with the algorithm.

No problem, only if you have the time and feel like it. :)

chglcu
May 17, 2007

I'm so bored with the USA.

Raenir Salazar posted:

No problem, only if you have the time and feel like it. :)

Heh, I'll see how far I make it tonight. It's getting a bit close to my bedtime now.

The 2.2 seconds (or whatever remains after the dictionary to array change) in your second pass is probably mostly due to the recursive root lookup. You're doing that lookup every pixel when it could be done once per label. You could lookup the real roots in a step before that pass 2 loop and probably cut that down quite a bit.

chglcu fucked around with this message at 05:30 on Oct 28, 2021

Raenir Salazar
Nov 5, 2010

College Slice

chglcu posted:

Heh, I'll see how far I make it tonight. It's getting a bit close to my bedtime now.

The 2.2 seconds (or whatever remains after the dictionary to array change) in your second pass is probably mostly due to the recursive root lookup. You're doing that lookup every pixel when it could be done once per label. You could lookup the real roots in a step before that pass 2 loop and probably cut that down quite a bit.

Thanks! It's getting close to my bed time too, as for the recursive root lookup; I think each time it happens it sets the root so successive lookups *should* be assigned because it updates the root no? Or am I misunderstanding it? If not, I will try to recompute the roots for the Labels before filling my new label array.

code:
        internal Label GetRoot()
        {
            if (this.Root != this)
            {
                this.Root = this.Root.GetRoot();//Compact tree
            }

            return this.Root;
        }
Does this not update as it goes along?

For the previous suggestion though I think the compiler is doing some sort of optimization because changing the code only results in a very slight, 0.03s improvement.

Code, I went and whole hogged it.
code:
        for (int y = 1; y < maxHeight; ++y)
        {
            int YxW = y * maxWidth;
            int Ym1xW = (y - 1) * maxWidth;
            for (int x = 1; x < maxWidth; ++x)
            {
                int index = YxW + x;
                int indexM1 = index - 1;
                int indexYm1X = Ym1xW + x;
                // check neighbours
                // check if West tile is the same colour as the current pixel
                if (ColourEquals(map[indexM1], map[index]))
                {
                    labelMap[index] = labelMap[indexM1];

                    if (map[indexYm1X].r == map[index].r &&
                        labelMap[indexYm1X] != labelMap[indexM1])
                    {
                        Labels[labelMap[index]].Join(Labels[labelMap[indexYm1X]]);
                    }
                }
                // check if South tile is the same colour as the current pixel
                else if (ColourEquals(map[indexYm1X], map[index]))
                {
                    labelMap[index] = labelMap[indexM1];
                }
                else
                {
                    // increment current label id
                    ++currentLabel;

                    // create new label with new id
                    Labels[currentLabel] = new Label(currentLabel);

                    // set the label to the label map
                    labelMap[index] = currentLabel;
                }
            }
        }

Raenir Salazar fucked around with this message at 05:44 on Oct 28, 2021

chglcu
May 17, 2007

I'm so bored with the USA.

Raenir Salazar posted:

Thanks! It's getting close to my bed time too, as for the recursive root lookup; I think each time it happens it sets the root so successive lookups *should* be assigned because it updates the root no? Or am I misunderstanding it? If not, I will try to recompute the roots for the Labels before filling my new label array.

code:
        internal Label GetRoot()
        {
            if (this.Root != this)
            {
                this.Root = this.Root.GetRoot();//Compact tree
            }

            return this.Root;
        }
Does this not update as it goes along?

It will still call the nested GetRoot() each time, since this.Root.GetRoot() won't be returning this most of the time. this.Root == this will only ever be true for one of the labels, as I'm understanding it.

Something like this would avoid recaculating it each time (though it does still do a null check each time, which could be avoided by doing this a bit differently, though it's more of a change):

code:
        private Label RealRoot = null;

        internal Label GetRoot()
        {
            if (this.RealRoot == null)
            {
                if (this.Root == this)
                {
                    this.RealRoot = this;
                }
                else
                {
                    this.RealRoot = this.Root.GetRoot();//Compact tree
                }
            }

            return this.RealRoot;
        }
I'm not actually sure how much impact this will have, since the dictionary Label lookup also affected the second pass. Worth a try though, I think.

As for the other optimizations, yeah, the compiler was probably taking care of most of it, though I would be surprised if it pulled y * maxWidth out of the inner loop on its own (I don't think the C++ compiler will).

From looking over that UnionFind article, I don't know that the approach it's talking about will help with optimizing the speed of building the initial data set you're currently working on. It sounds like its more about making use of the data later faster, which may or may not matter with the size of your data set (number of labels in your case, I believe?), but probably want someone with a brain that's still fully functional to confirm that.

chglcu fucked around with this message at 06:03 on Oct 28, 2021

Jabor
Jul 16, 2010

#1 Loser at SpaceChem
Your Union implementation is wrong - when you join two elements, you need to find the roots of each tree and join those, rather than just joining the elements you're given.

The Find implementation is correct, but probably isn't optimal - I wouldn't expect the compiler or jitter to attempt to inline recursive calls, so you're paying function call overhead every time even when you're only one step from the root. Look up and implement one of the iterative Find implementations instead.

Lastly, you're allocating each label in a separate allocation which means they're going to be scattered around the heap. Consider making Label a value type, have an array that holds all the labels, and refer to each label purely by index in that array.

If you set up your struct so that when it's zero-initialized, that correctly indicates a new label that hasn't been joined with anything yet, you can totally skip initializing labels and just keep a counter referring to the first unused one. For example:

code:

struct Label {
  bool isChild;
  int parentIndex; // only valid when isChild
  int rank; // only valid when !isChild
}

(If you're clever, you can then notice that parentIndex and rank are never used at the same time, and you just need the bool and one int. If you're really clever, you can store the bool in one bit of the int as long as you're never going to need more than 2^31 different pre-merge labels).

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 can't immediately tell from the code, did you ever actually migrate it to a scanline-based floodfill algorithm? Because if not that's still going to be a huge saving.

Also, I really appreciate that you're making an effort to do this, because I feel like many AAA games (e.g. XCom 2) don't give a gently caress that they take 30 seconds to prepare a level that should be the work of [negligible fraction] seconds.

Raenir Salazar
Nov 5, 2010

College Slice

roomforthetuna posted:

I can't immediately tell from the code, did you ever actually migrate it to a scanline-based floodfill algorithm? Because if not that's still going to be a huge saving.

Also, I really appreciate that you're making an effort to do this, because I feel like many AAA games (e.g. XCom 2) don't give a gently caress that they take 30 seconds to prepare a level that should be the work of [negligible fraction] seconds.

I'm currently using Connected Component Labeling which I understand is basically like scanline; but should be better. It scans through every pixel, assigns a label; keeps track of which labels are basically equivalent and then on a second pass merges them.

The "One component at a time" version is basically scanline floodfill. Which in hindsight when I tried it I might've been able to get it to go faster with the optimizations that were suggested to me since then for the two pass. I'd have to go and look but its presumably not absurdly faster since its checking lots of pixels redundantly.

And yeah I'm generally fairly interested in performance, some of the neat tricks I see every once in a while just really impresses me and just feels like the best way to improve as a programmer isn't just using the right algorithm but also finding ways to squeeze good performance out of it.

Jabor posted:

Your Union implementation is wrong - when you join two elements, you need to find the roots of each tree and join those, rather than just joining the elements you're given.

The Find implementation is correct, but probably isn't optimal - I wouldn't expect the compiler or jitter to attempt to inline recursive calls, so you're paying function call overhead every time even when you're only one step from the root. Look up and implement one of the iterative Find implementations instead.

Lastly, you're allocating each label in a separate allocation which means they're going to be scattered around the heap. Consider making Label a value type, have an array that holds all the labels, and refer to each label purely by index in that array.

If you set up your struct so that when it's zero-initialized, that correctly indicates a new label that hasn't been joined with anything yet, you can totally skip initializing labels and just keep a counter referring to the first unused one. For example:

code:
struct Label {
  bool isChild;
  int parentIndex; // only valid when isChild
  int rank; // only valid when !isChild
}

Thanks for noticing this. :) As for the value type idea I found that when I did that for previous experiments for the else block it didn't do a whole lot, like 0.1s improvement but I'll try it.


To clarify which Union implementation is wrong, the one from Here? Or is it the other one? I'm currently using the one I linked from CodeProject.

quote:

(If you're clever, you can then notice that parentIndex and rank are never used at the same time, and you just need the bool and one int. If you're really clever, you can store the bool in one bit of the int as long as you're never going to need more than 2^31 different pre-merge labels).

One step at a time before we potentially engage in witch craft! :)

I think the first step is fixing the Union code, as chglcu notes maybe the code from the codeproject article might not be what I want. But the code from the medium article/github confuses me as its unclear to me if its the same usage as the other. github link, medium article describing it

It seems like in the Medium article they seem to know their data ahead of time so they preallocate the structure with the size of their data, and then start performing union operations on those indexes? I'm not sure if this structure is equivalent to the one I implemented/copied and just need to use it the same way. It's really unclear to me and other people when I google it seem to use a similar implementation; or something entirely different.

Like is it:
code:
UnionFind myUnion = new UnionFind(20000); // rough guess of the max number of labels
...
myUnion.Union(currentLabel, SomeLabelNearBy);
or is it meant to be:

code:
UnionsDictionary.Add(currentLabel, new UnionFind(currentLabel));
...
UnionsDictionary[currentLabel].Union(currentLabel, SomeLabelNearBy);
UnionsDictionary[SomeLabelNearBy].Union(currentLabel, SomeLabelNearBy); // do I also need to do it in reverse?
but with the suggestion that I re-implement it as a value type struct:
code:
UnionFind[] myUnions = new UnionFind[20000];
...
myUnions[currentLabel].parentIndex = currentLabel; // ??? or rank? Or is rank set with union?
...
myUnions[currentLabel].Union(myUnions[currentLabel].parentIndex, myUnions[SomeOtherIndex].parentIndex); //??? the github/medium implementation
// doesn't accept another Union as a parameter which makes me think its just 1 object that's meant to be instantiated?

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
I'm looking into adding Steam Workshop support to my game. I already support modding in general, so this is just to make it easier for players to access mods. From reading through Steam's guide, it sounds like the steps involved are basically:
  • When the player starts the game, query Steam to see what mods they're subscribed to.
  • Download any necessary files and stick them into my "Mods" folder. (Should I auto-enable these modes as well? Downloaded mods aren't enabled by default, currently)
  • Delete/hide any local files associated with mods that the player is no longer subscribed to, so they don't show up in the mods list. This implies that I'll need some way to differentiate workshop mods from non-workshop mods, so that I don't go wiping the Mods folder of everything.
  • Add an in-game form that allows users to upload a specific mod to the Workshop as their own work. Do I need to do any validation that they don't just re-upload a mod they just downloaded?

Is that accurate? I'm assuming that the process of finding and subscribing to mods is done by the user in the Steam client, so I don't need to build a mod browser in-game. That sounds like a lot of work that I'd prefer to avoid, personally.

I would prefer not to curate the workshop, so that I'm not in the loop every time someone wants to change things. However, I fully expect that people will try to add objectionable mods for my game, specifically mods showing Nazi / white supremacist / Imperial Japanese symbols. If I use a non-curated workshop, do I have any moderator powers as the owner of the game?

HappyHippo
Nov 19, 2003
Do you have an Air Miles Card?

As luck would have it, I'm actually doing something at work that involves union-find.

Anyway, I'd suggest using the struct, and implementing the operations (MakeSet, Find, and Union) as per this article:
https://en.wikipedia.org/wiki/Disjoint-set_data_structure#Operations
You're probably better off implementing them as functions rather than methods (or make the whole forest a class and implement them as methods on that). Use the versions that aren't recursive, like this one:
code:
function Find(x) is
    while x.parent != x do
        (x, x.parent) := (x.parent, x.parent.parent)
    end while
    return x
end function
You don't have to find the roots before calling Union when Union is implemented correctly:
code:
function Union(x, y) is
    // Replace nodes by roots
    x := Find(x)
    y := Find(y)

    if x = y then
        return  // x and y are already in the same set
    end if

    // If necessary, rename variables to ensure that
    // x has rank at least as large as that of y
    if x.rank < y.rank then
        (x, y) := (y, x)
    end if

    // Make x the new root
    y.parent := x
    // If necessary, increment the rank of x
    if x.rank = y.rank then
        x.rank := x.rank + 1
    end if
end function

HappyHippo fucked around with this message at 21:01 on Oct 28, 2021

Peewi
Nov 8, 2012

TooMuchAbstraction posted:

I'm looking into adding Steam Workshop support to my game. I already support modding in general, so this is just to make it easier for players to access mods. From reading through Steam's guide, it sounds like the steps involved are basically:
  • When the player starts the game, query Steam to see what mods they're subscribed to.
  • Download any necessary files and stick them into my "Mods" folder. (Should I auto-enable these modes as well? Downloaded mods aren't enabled by default, currently)
  • Delete/hide any local files associated with mods that the player is no longer subscribed to, so they don't show up in the mods list. This implies that I'll need some way to differentiate workshop mods from non-workshop mods, so that I don't go wiping the Mods folder of everything.
  • Add an in-game form that allows users to upload a specific mod to the Workshop as their own work. Do I need to do any validation that they don't just re-upload a mod they just downloaded?

Is that accurate? I'm assuming that the process of finding and subscribing to mods is done by the user in the Steam client, so I don't need to build a mod browser in-game. That sounds like a lot of work that I'd prefer to avoid, personally.

I would prefer not to curate the workshop, so that I'm not in the loop every time someone wants to change things. However, I fully expect that people will try to add objectionable mods for my game, specifically mods showing Nazi / white supremacist / Imperial Japanese symbols. If I use a non-curated workshop, do I have any moderator powers as the owner of the game?

Many games put the mod uploading in a separate tool rather than the game itself, but I don't think there are any strict requirements about that.

While not required, I think it's nice when a game can react to new Workshop subscriptions without having to relaunch the game.

Moderating the Workshop is described on the Community Moderation page of the Steamworks documentation.

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

Peewi posted:

Many games put the mod uploading in a separate tool rather than the game itself, but I don't think there are any strict requirements about that.

While not required, I think it's nice when a game can react to new Workshop subscriptions without having to relaunch the game.

Moderating the Workshop is described on the Community Moderation page of the Steamworks documentation.

Thanks, sounds like my understanding is basically correct, which is good. Good call on being able to refresh subscriptions. My game has a very fast launch time (basically nothing loads until you actually go into a level), but given that I already have the ability to turn mods on and off without relaunching, I might as well also review the subscription list on the fly as well.

Regarding moderation: that's a shame, that I don't have dictatorial control over content. What happens if someone uploads mods that add the Confederate flag? Or even the Imperial Japanese Navy flag? An awful lot of people don't know how problematic the rising sunburst flag is.

One last question: how does testing work? I'd need to be able to add workshop mods without actually making all that stuff public...

KillHour
Oct 28, 2007


People are gonna do that poo poo whether you like it or not and you're not going to be able to police all of it.

Jabor
Jul 16, 2010

#1 Loser at SpaceChem

To be clear, the Union implementation in that CodeProject article is wrong. It might be sufficient for the exact use case the article author applies it to - proving that it is could be tricky. It's not correct as a general implementation.

The wikipedia article HappyHippo linked is fantastic, it's a much better reference on UnionFind than any random hands-on tutorials you're going to see floating around. I would recommend implementing it yourself based on the wikipedia article - it's a good learning experience.

--

On performance, the big secret (and it's not really a secret) is that having lots of tiny little object instances sucks for performance. They're great for having flexibility and being able to rapidly iterate on things through the design phase, but once you know exactly what you're doing and you know it needs to be high-performance, you really want to get away from objects and focus on a more data-oriented programming model. As a rather trite example, when you have an image, you don't model it as a big list of pointers to individually-allocated Pixel objects - instead it's a big array containing the pixel data directly.

Peewi
Nov 8, 2012

TooMuchAbstraction posted:

One last question: how does testing work? I'd need to be able to add workshop mods without actually making all that stuff public...

https://partner.steamgames.com/doc/features/workshop
There's a "Running a private beta" section at the bottom.

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

Peewi posted:

https://partner.steamgames.com/doc/features/workshop
There's a "Running a private beta" section at the bottom.

:doh: of course, thank you for having better reading comprehension skills than I do!

Raenir Salazar
Nov 5, 2010

College Slice
Alrighty I took a crack at it using the wiki article that HappyHippo linked and without cheating.

I started with the assumption that I do need some sort of array to store nodes and that this needs to be preallocated; if I ever need more than the amount allocated probably best to do the reallocate by multiplying the size by 2 or something.

code:
    private LabelNode[] labelNodes; // initialize when needed, to idk, 20,000?

    public struct LabelNode
    {
        public bool isChild; // ??
        public int parentIndex;
        public int rank;
    }

    // check if NodeX is contained by NodeY?
    private int Find(int InNodeX)
    {
        int root = InNodeX;
        while (labelNodes[root].parentIndex != root)
        {
            root = labelNodes[root].parentIndex;
        }

        while (labelNodes[InNodeX].parentIndex != root)
        {
            int parent = labelNodes[InNodeX].parentIndex;
            labelNodes[InNodeX].parentIndex = root;
            InNodeX = parent;
        }

        return root;
    }

    private void Union(int InNodeX, int InNodeY)
    {
        int RootNodeX = Find(InNodeX);
        int RootNodeY = Find(InNodeY);

        if (RootNodeX == RootNodeY) return;

        if (labelNodes[RootNodeX].rank < labelNodes[RootNodeY].rank)
        {
            // dunno if C# has a way of "swapping" two variables at the same time
            RootNodeX = RootNodeY;
            RootNodeY = InNodeX;
        }

        labelNodes[RootNodeY].parentIndex = RootNodeX;

        if (labelNodes[RootNodeX].rank == labelNodes[RootNodeY].rank)
        {
            ++labelNodes[RootNodeX].rank;
        }
    }
It tripped me up a bit but "parent" and "root" I figured out in the end must be local variables and not references.

I opened up paint and did some trivial examples by hand and it seems to work?

@Jabor I'm not quite sure what isChild is for, as it seems like whenever I assign a new label I can just set the parentIndex to itself and that should work?

I'll go and attempt to integrate it and let you know if it explodes.

e: Seems to run and is a whole second faster; first+second passes now take 4.15s. :cool: :cheers:

That suggests to me first pass maybe takes 3s and second pass is now down to 1s or similar as that inner-if statement:


code:
                    if (map[BelowRow + x].r == map[SameRow + x].r &&
                        labelMap[BelowRow + x] != labelMap[SameRow + x - 1])
                    {
                        Union(labelMap[SameRow + x], labelMap[BelowRow + x]);
                    }
IIRC adds about 1s to the run time which was originally 1.8s.


Assuming this worked and didn't horribly fail in a non-crashy way.

second pass is now:
code:
        for (int i = 0; i < WxH; ++i)
        {
            newLabelMap[i] = Find(labelMap[i]);
        }
Hilarious saving the two 8k by 4k textures to disk for my height map and silhouette map take about 3 seconds so they're rivaling each other for my total runtime when I hit play. Since I don't need those textures in the actual runtime of the problem as I have the rendetextures maybe I should put my SaveTextureToDisk function as a Coroutine so it stops freezing the editor.

e2: Apparently according to my profiler; the total runtime of my Start() is about 7.5s; with about 5.2s being Start() itself (with 4s being the twopass algo, 1.2s being everything else) and then 2s being my coroutine attempt to save an image to disk in a non-blocking way but it seems like that didn't work?

Raenir Salazar fucked around with this message at 06:19 on Oct 29, 2021

KillHour
Oct 28, 2007


A coroutine is still going to block code execution unless you specifically yield back to the main program. You need an async disk write with a callback to notify you when it's done.

Raenir Salazar
Nov 5, 2010

College Slice

KillHour posted:

A coroutine is still going to block code execution unless you specifically yield back to the main program. You need an async disk write with a callback to notify you when it's done.

Yeah the tutorial script here: https://docs.unity3d.com/ScriptReference/WaitForEndOfFrame.html seems to clarify that all this coroutine does is pushes execution until after some other stuff is done. Hrm.

e: Wow, there's... Not a whole lot of information out there, this is unfortunate.

Raenir Salazar fucked around with this message at 07:56 on Oct 29, 2021

Jabor
Jul 16, 2010

#1 Loser at SpaceChem

Raenir Salazar posted:

@Jabor I'm not quite sure what isChild is for, as it seems like whenever I assign a new label I can just set the parentIndex to itself and that should work?

The idea is that you don't need to initialize anything at all - since the 0 state already represents "a self-contained tree that hasn't been joined to anything". That way you don't need to touch the labels array if you're just creating new values, only when you're actually unioning things. If you're not using it just delete it from the struct, making your array fit the same number of entries in less memory will improve your cache hit rate and thus performance.

Raenir Salazar posted:

code:
        if (labelNodes[RootNodeX].rank < labelNodes[RootNodeY].rank)
        {
            // dunno if C# has a way of "swapping" two variables at the same time
            RootNodeX = RootNodeY;
            RootNodeY = InNodeX;
        }

This is wrong - if you're joining a small X tree to a larger Y tree, now all the parents of the actual node being joined will be dropped.

You could just do a three-line swap with a temporary variable:
code:
int tmp = RootNodeX;
RootNodeX = RootNodeY;
RootNodeY = tmp;
Or you could forego swapping and just write an if-else ladder that might be easier to reason about :

code:
if (labelNodes[RootNodeX].rank < labelNodes[RootNodeY].rank) {
	labelNodes[RootNodeX].parentIndex = RootNodeY;
} else if (labelNodes[RootNodeX].rank > labelNodes[RootNodeY].rank) {
	labelNodes[RootNodeY].parentIndex = RootNodeX;
} else {
	labelNodes[RootNodeX].parentIndex = RootNodeY;
	labelNodes[RootNodeY].rank++;
}

Raenir Salazar
Nov 5, 2010

College Slice
Ah for some reason I thought InNodeX and RootNodeY were identical but forgot that RootNodeX is the result of the Find operation on InNodeX. Thanks for catching that. :)

e: I am just having ZERO luck getting asynchronous "write a texture to disk" functionality to work, something always pops up to make it not really work; with currently my original coroutine implementation so far working best.

I tried making a Unity Job but needing to do .Complete() on the job handler basically makes it not work since I did it right away and I want this to be in my static texture generator class so I don't really have a convenient option of putting it into late update; maybe shoving it into a coroutine would work but that seems like overcomplicating it.

I'll try .Net C# stuff next like File.WriteAllBytesAsync since maybe all I need to do is pass it the raw byte data from my Texture2D?

2 seconds to print my images isn't the end of the world but it'd be nice to print these textures in the background as I complete various steps of the generation process for debugging purposes without it contributing to editor lag.

edit 2, successeroo:
code:
    public static async Task SaveTextureAsPng(Texture2D texture, string path, string InName)
    {
        byte[] data = texture.GetRawTextureData();
        UnityEngine.Experimental.Rendering.GraphicsFormat graphicsFormat = texture.graphicsFormat;
        int width = texture.width;
        int height = texture.height;

        await Task.Run(async () =>
        {
            byte[] bytes = ImageConversion.EncodeArrayToPNG(
                data,
                graphicsFormat,
                (uint)width,
                (uint)height
            );

            using (var filestream = new FileStream(path + InName, FileMode.Create))
            {
                await filestream.WriteAsync(bytes, 0, bytes.Length);
            }
        });
    }
(fake edit, a friendo from discord suggested changing void to Task; apparently this is better practice)

I had trouble with getting various attempts at asynchronous code to work, but this seems to work. From 3.5s to 0.5s to run the "save to disk" block.

e again: I had the world's stupidest bug.

code:
                // check if South tile is the same colour as the current pixel
                else if (map[BelowRow + x].r == map[SameRow + x].r)
                {
                    labelMap[SameRow + x] = labelMap[[b]SameRow + x - 1[/b]];
                }
When it should have been BelowRow + x!

As my labels seemed to weirdly converge to a random number.

e whatever:

WHAT THE HECK IS THIS.



:aaa:

Probably my shader code

e: Apparently it was my shaders, solution was to divide by id.x and id.y by 4.

code:
[numthreads(16, 16, 1)]
void ColourTexture(uint3 id : SV_DispatchThreadID)
{
    if (id.x > reses.x || id.y > reses.y)
    {
        return;
    }

    uint idx = id.x / 4;
    uint idy = id.y / 4;

    int index = (idy * reses.x) + idx;

    float4 pixelColour = colours[LabelGrid[index]];

    Result[id.xy] = float4(pixelColour.x, pixelColour.y, pixelColour.z/*(ed)*/, 1);
}
I'm honestly kind of confused; I think I have the work area divided into 16 work groups along x and why axis; and there seems to be some relation as 4x4 is 16 but still don't get why.

e: Some trial and error and I think it has to do with the stride of my input data for the label; which is 16 bytes.

e: Confirmed! By stride for the size of each element was 16 bytes, but according to the documentation the stride of an int is 4 bytes so my data was 4x larger than it was supposed to be, which weirdly resulted in my shader just painting it 4 times. By removing the division by 4 and setting the stride to 4 it works perfectly now.

Somehow I figured this out, its a good feeling.

Yeeeeeeeeeeeeeeeeeeeeeah!


With rerandomized colours!

Raenir Salazar fucked around with this message at 07:58 on Oct 30, 2021

Raenir Salazar
Nov 5, 2010

College Slice
For some reason *three pixels* aren't right. The code seems to result in 3 additional water pixels and 3 less land pixels out of 34,000,000. This is perplexing, I'm currently working on outputting the exact results of the labelling process to compare with the pre-labelling silhouette process to see which 3 pixels are wrong.

e: It could be that the Paint.Net magic wand tool isn't properly selecting all the right pixels but it would be weird to me for it to only be 3.

e2, found the errant pixels; now to figure out why these 2 regions (it's a region of 2 pixels and a region of 1 pixel some distance away) weren't categorized properly.

e3: Solved! It was something *extremely* silly and never would have caught it otherwise.

I had:

code:
            coords[i].x = i % width;
            coords[i].y = Mathf.RoundToInt(i * widthInverse);
When it needed instead to be:
code:
            coords[i].x = i % width;
            coords[i].y = (int)(i * widthInverse);
As I just want it to take the integer and not the remainder when calculating our row.

Raenir Salazar fucked around with this message at 03:55 on Nov 4, 2021

VideoGameVet
May 14, 2005

It is by caffeine alone I set my bike in motion. It is by the juice of Java that pedaling acquires speed, the teeth acquire stains, stains become a warning. It is by caffeine alone I set my bike in motion.
Hello,

I have a potential contract to create a political game with similar functionality to the PeaceMaker game, but on a different topic (Climate) with some visual novel elements:

http://www.peacemakergame.com

Normally I'd just do this in Ren'Py (python based visual novel engine) but this needs to be web based and can't be dependent on large plug-ins.

Basically the game has events you respond to, policies you implement, characters that advise and comment, and polling of various interest groups .. oh and the excitement of budgeting!

So I need to find a decent toolset for creating a responsive web app. Ren'Py's HTML version isn't stable so that's not an option. Unity's web stuff requires the WebGL thing, and I'm pretty sure this has to work on Chromebooks.

Looking at LiveCode and Flutter as well as some visual novel engines designed for HTML (Tuesday-js) etc.

Suggestions?

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!

VideoGameVet posted:

Looking at LiveCode and Flutter as well as some visual novel engines designed for HTML (Tuesday-js) etc.

Suggestions?
I would suggest don't use Flutter. I transitioned a project of mine to Flutter, got it to the point of being ready for a test run, then found that it worked in Android, it worked in desktop-Chrome, but some widgets were invisible in Android-Chrome. It (Flutter, not my project) also has many horrible performance bugs and testing issues that basically nobody gives a poo poo about fixing. So I'm now half way through transitioning it away from Flutter again and back to raw Typescript like it was in the first place.

So that's my negative suggestion. My positive suggestion is to just use raw Typescript or Javascript, it's pretty functional these days, and all the frameworks basically just add an additional layer that constrains what you can do, is a potential source of bugs, and is a source of risk (i.e. the framework gets a new version that isn't backwards compatible, it fixes some bugs, and you have to decide whether to migrate or not). Progressive Web Apps are also sufficiently mainstream now that for Android at least they're pretty viable as a "looks like it's running a real app" thing. (My understanding is that iPhone support at the installation level is still a bit dubious.)

roomforthetuna fucked around with this message at 20:15 on Nov 6, 2021

barkbell
Apr 14, 2006

woof
I want to make a game. I guess ill do a pong clone to learn. Rust is good for games right? Any frameworks?

Raenir Salazar
Nov 5, 2010

College Slice
I feel it doesn't really get any better than Unity3D or Unreal for making your first games; the engine does a lot of stuff for you that you'd have to do from scratch and get frustrated by if you're programming without an engine. Apparently Rust is similar to C++ so maybe you should check out Unreal and work with Blueprints? :)

One thing I did when I was a games programming instructor (for a highschool) was basically just modified existing Unity3D tutorials; so you could take Unreal tutorials and once you finish them, think of ways you'd like to extend them to be more fun or interesting or have more options.

barkbell
Apr 14, 2006

woof
i also bought a lil wacom and aseprite for doing pixel assets. whats a good cheap/free music program? i wanna make some dope music a la my favorite games like secret of mana or sonic the hedgehog

im thinking rust because i want to learn rust not just for games. i feel lilikeke it would be a good langauge to learn since amazon has leaned into it and it would help my career (day job im a dev making some real time web app serious business applications or whatever)

12 rats tied together
Sep 7, 2006

if your day job is web dev you should consider checking out html5/javascript stuff like pixi.js, three.js, etc., and consider building your game with electron. rust has node-js interop via neon, and electron makes it easy to run stuff you can't normally run on the web, like a client side node-js module that runs rust code, alongside the rest of your web stuff

it also makes the UI really easy to make :)

Bongo Bill
Jan 17, 2012

As a language, Rust is poised to be excellent for game develepment, insofar as it can be used for both high-level abstractions and high-performance bit-twiddling, and most importantly, it does all that without a garbage collector. However, the game dev tooling ecosystem in Rust is nowhere near as robust as the established commercial tools. It all depends on how much extra work you're willing to put in to make up for that lack of parity. You might check out Are We Game Yet? to get a sense of the state of the art.

barkbell
Apr 14, 2006

woof
ok ill check out pixi.js (there is also phaser too i think)

thank you for the 'are we game yet' link. that is super helpful.

anyone have any suggestions for music making apps? i messed around with garage band when it came out 15 years ago and made a couple songs so thats about the extent of my knowledge. i found lmms and it seems alright.

cultureulterior
Jan 27, 2004

VideoGameVet posted:


So I need to find a decent toolset for creating a responsive web app. Ren'Py's HTML version isn't stable so that's not an option. Unity's web stuff requires the WebGL thing, and I'm pretty sure this has to work on Chromebooks.

Looking at LiveCode and Flutter as well as some visual novel engines designed for HTML (Tuesday-js) etc.

Suggestions?

Godot's web support is pretty good, and their DSL is pretty close to python

giogadi
Oct 27, 2009

barkbell posted:

ok ill check out pixi.js (there is also phaser too i think)

thank you for the 'are we game yet' link. that is super helpful.

anyone have any suggestions for music making apps? i messed around with garage band when it came out 15 years ago and made a couple songs so thats about the extent of my knowledge. i found lmms and it seems alright.

Music making is a deep rabbit hole. There are a million different approaches to it, but the key is to just pick one bit of software and commit to learning it. Idk anything about lmms but it seems fine. I personally use Ableton but the specific software honesty isn’t that important. Experiment with that, explore your options as far as instrument plugins, and have a good time.

If you’re specifically interested in making very authentic chiptune music like from the 16-bit days, there are instrument plugins (search “chiptune plugin” or “sega genesis plugin” or whatever to get started) that will emulate those sounds if you like.

nurmie
Dec 8, 2019

barkbell posted:

anyone have any suggestions for music making apps? i messed around with garage band when it came out 15 years ago and made a couple songs so thats about the extent of my knowledge. i found lmms and it seems alright.

personally, i like Reaper - it's compact, multiplatform, pretty cheap for a DAW and pretty easy on the system resources too. plus, it has a nice collection of lightweight but super useful VST plugins covering all the basics. the only drawback is you don't get any VST instruments out of the box (unlike, say, FL Studio or Logic). however, there's buttloads of free and quality vsts to be found online, so it's not that big of a dealbreaker imo

that said, yeah, most DAWs nowadays are alright and it's really more of a question of aesthetics and workflow preferences rather than, say, sound quality (all DAWs sound the same anyway)

nurmie fucked around with this message at 19:01 on Nov 7, 2021

VideoGameVet
May 14, 2005

It is by caffeine alone I set my bike in motion. It is by the juice of Java that pedaling acquires speed, the teeth acquire stains, stains become a warning. It is by caffeine alone I set my bike in motion.

cultureulterior posted:

Godot's web support is pretty good, and their DSL is pretty close to python

Found something called: https://monogatari.io


"Monogatari is just a webapp that uses pure HTML, CSS, and Javascript, with nothing special. The only plugns it uses are included from the get go and will require no downloads from your users. It's all self contained in the web page."

Might do the trick.

VideoGameVet fucked around with this message at 06:53 on Nov 8, 2021

VideoGameVet
May 14, 2005

It is by caffeine alone I set my bike in motion. It is by the juice of Java that pedaling acquires speed, the teeth acquire stains, stains become a warning. It is by caffeine alone I set my bike in motion.
Godot is cool, but the web stuff requires the WebAssembly stuff and I don’t think it will work on Chromebooks.

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

VideoGameVet posted:

Godot is cool, but the web stuff requires the WebAssembly stuff and I don’t think it will work on Chromebooks.

Godot's object model always seemed like it would make optimizing basically anything very difficult. Then again most games don't have enough things moving around where it would be a significant issue on modern hardware. :shrug:

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!

VideoGameVet posted:

Godot is cool, but the web stuff requires the WebAssembly stuff and I don’t think it will work on Chromebooks.
Are you sure about this? Even an article from 2018 seems to think WebAssembly on Chromebooks is okay.

I just sent https://godotengine.github.io/godot-demo-projects/2d/bullet_shower/ to my wife, and it runs on her Chromebook (though it ran kinda shittily until the screen was full of bullets, then was smooth after that, a bit weirdly - I guess something with the allocation of objects).

Adbot
ADBOT LOVES YOU

Raenir Salazar
Nov 5, 2010

College Slice
Spent a few hours but finally got my basic compute shader working to output 1 region:



My plan basically is to use this result as a masking layer of sorts for my voronoi shader so the extents of the voronoi regions only cover "land" pixels.

To simplify matters I might do this one at a time for each "region" and then overlay them to form the final image.

I'm debating whether its worthwhile to try to calculate (currently on the CPU) the bounding box of each region, the centroid of each region could be useful information down the road. On the other hand, doing minimax on basically every pixel is going to slow things down significantly; unless I find a clever work around.

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