Due to a security update, you may have to reset your password. Don’t panic, nothing has gone wrong and your password is safe. If you don’t have access to that email, send Tube a message at [email protected] More info here: https://status.vanillaforums.com/incidents/2zdqxf3bt7mj
Our new Indie Games subforum is now open for business in G&T. Go and check it out, you might land a code for a free game. If you're developing an indie game and want to post about it, follow these directions. If you don't, he'll break your legs! Hahaha! Seriously though.

[Game Dev] I don't have a publisher. What I do have are a very particular set of skills.

1575859606163»

Posts

  • KupiKupi Registered User regular
    Kupi's Weekly Friday Status Report

    My goal from two weeks ago was somewhat ambitious: get to the point where I could run a program that, using my own game files, jumps straight into gameplay defined by content files. And thanks to having two weeks to do it, I managed to accomplish that goal! ... sort of. Technically.

    See, I kind of forgot to update my project editor to support the new paradigm for content loading, and then edit my test project files to actually load that content. So, I've confirmed through a debug session that my framework correctly loads a level file given by the command line or the configuration file, extracts a context, populates that context with system instances, and then runs the systems in the contexts ad infinitum. The problem is that my test set currently loads a base-class context instance with no systems in it, and none of my "batteries included" components have required systems defined. So the data is all there, but the game doesn't actually do anything with it. It just renders an open field of Cornflower Blue.

    Suffice it to say, my task for the coming week will be to patch up the Project Editor so that I can make an honest test of the content loading pipeline.

    ---

    In unrelated game-development news, my sister is currently in the process of making a pretty severe career swing from zoology (in which she has her degree) to programming (which she fell into after turning out to be the most intelligent person at a major multinational company's finance department and self-tutoring herself to proficiency in SQL). She's currently exploring job opportunities in data analysis, most of which ask that you work with Python. To that end, she's been learning Python from various tutorials online, and has started to build a basic choose-your-own-adventure game. She walked me through the first couple decisions, most of which involve typing a (l)etter that indicates your choice. After choosing to equip myself with a (f)lashlight, turn (b)ack when I heard a strange noise coming from up ahead, and then (r)un away as it got closer, I was faced with a woman on a motor scooter demanding that I guess what her favorite programming language is. I said "Python". My sister typed "python".

    And it crashed.

    It never fails~

    My favorite musical instrument is the air-raid siren.
    GlalIanatorRoyceSraphim
  • eelektrikeelektrik Registered User regular
    In my senior year at university, and for my two-quarter long capstone game class we have a team of 4 making this game idea I had before of making a twin-stick shooter inspired by tabletop RPGs where you control a D20 rolling around the level by tilting it.

    Short clip of our current progress

    steam_sig.png
    ElvenshaeDisruptedCapitalistDaenrisLilnoobsRoyceSraphim
  • KupiKupi Registered User regular
    Kupi's Weekly Friday Status Report

    My goal was to get to the point where I could load up a level from a file at program start, basically breaking through to the point where I'm doing game programming rather than architectural work, and, well, the old saw about the last 10% being 90% of the work applies as well as ever and also I'm lazy. There turned out to be a bunch of small tasks I'd left unfinished that had to be resolved, and I didn't get through them all. These included:

    - I started working on a "buffered component query" where the Components are returned in an array (which lets you do repeat random access without going through several layers of dictionary lookups each time). I stopped working on that when I realized that it wasn't necessary to what I was trying to accomplish (rewriting the sprite drawing system), so right now there's a NotImplementedException sitting in a deep stratum of the code waiting to be rediscovered at an inopportune time.
    - I rewrote the sprite drawing system, because it turned out that my original one assumed a completely static camera. The new system actually takes the position of a camera component into account, so you can display objects that are further than a screen's width from the origin.
    - I applied some class attributes that make sure that, for instance, when you have an entity with a sprite component, you always get a sprite drawing system to draw them.
    - Some work in the level editor to editor some attributes of a level that I forgot to give you the ability to edit before.
    - And numerous other one-liner edits not worth your or my time to document.

    On the design side, I think I'm going to wind up abandoning my attempt to have just one single "Project Editor" that I could use to edit content files for games made with my architecture. The idea was to load the binaries with the game code in them so I could display the classes they defined in the editor, but so far all attempts to load code from a different directory than the one I'm currently in have resulted in access denial errors. I'm sure there's some way around it through code signing or whatever, but it's far, far easier to simply turn my project editor into a library and create a custom editor application for each game that references the same libraries as the game. I lose the ability to let people mod my games in terms of behavior, but at the same time having my code load up code from a third party that runs on someone else's machine sounds like more than I want to be responsible for, and console makers hate it when you become an arbitrary code execution vector. It's not as though I'm going to be making a hundred games on this platform, so giving each of them a separate editor project isn't a huge tax.

    This week's goal is the same as the last three weeks: hit "debug" and make a game show up.

    My favorite musical instrument is the air-raid siren.
    dipuc4lifeLilnoobs
  • RoyceSraphimRoyceSraphim Registered User regular
    Trying to make incremental progress and tossed in a stock command.

    Said command worked, and bouncing is working normally?

    Progress?

    dipuc4lifeElvenshaeLilnoobs
  • GlalGlal Registered User regular
    This feels relevant.

    RoyceSraphimKoopahTroopahdipuc4life
  • IzzimachIzzimach Fighter/Mage/Chef Registered User regular

    Since I actually got some things done this week I"ll write a little end-of-week post, Kupi-style.

    I set up the "world map" which is just a bunch of randomly arranged hexagons really. Dimmed out hexes are currently unavailable. Each hex is basically a level with a little mini-quest; when you complete the quest for that hex all the adjacent hexes are made available. In this way the player gets some choice about where to go next without getting overwhelmed. The map doubles as a quest log since you can see which hexes are "done"

    8uu1d554w9fr.jpg

    The second thing I did was set up objects that the player can interact or talk with. The screenshot below is a spirit that grants the player the ability to shapeshift into an animal. (a bear in this case).

    The different animal shapes are used to complete stages of puzzles. I made this concept on a smaller scale in a game called Chartreuse Warden, the second game in this list here

    1qbhbz3x8rqi.jpg

    IanatorElvenshaeKupidipuc4lifeRoyceSraphimCornucopiistDisruptedCapitalist
  • KupiKupi Registered User regular
    oh god name recognition

    Kupi's Weekly Friday Status Report

    Apropos of that "perfectionism" video, I am now further from my goal of loading into a playable level than I was when I started, because I realized that the way I handle level scrolling was philosophically flawed and had to be replaced entirely. Without going into too much detail, I realized that when my level-scrolling system detects movement on a particular axis, even if it's operating on a small subset of objects that could potentially spawn in, it's still performing a sweep that scans all the way across the entire world on that axis. Worse, there was no way to reasonably initialize the set of spawners that were already within the window without, again, scanning across potentially the entire world.

    Fortunately, I've already devised a system for fast detection of objects in a specific window in 2D space for my collision detection system, and it's no trouble to adapt it for the purpose of determining which entities ought to spawn in. Unfortunately, I realized that my sliding-window system has another problem, which I've dubbed the "Flaky Goomba Problem". Consider this situation, expressed in the recognizable terms of Super Mario Brothers: on a particular level, there's a recess one block deep, in which a single Goomba plods back and forth, bouncing against the walls and reversing course, as Goombas do. An inquisitive player discovers that if they stand on a particular block for several seconds before advancing, the Goomba doesn't appear in the pit. But why?

    Here's a picture to help visualize it:

    c0ekid4pka6k.png

    In this situation, the scrolling window (the dotted line) has advanced far enough to spawn in the Goomba, but not far enough to spawn in the wall on the other side of the pit. The Goomba, now in play, ambles forward, bounces off of the wall, and walks back, off the side of the world, and eventually hits the point where the game decides to delete it from play. Since the window never crosses back over the spawner, the pit is just mysteriously empty when Mario gets to it.

    I'm considering multiple solutions to this. The first one that occurred to me was to have two different level scrolling windows, one of which is further out from the camera and spawns in "passive" objects like terrain, while the other is closer and spawns "active" objects like enemies. That way I can be reasonably certain that solid objects on which moving objects rely will be loaded before the moving things need them. Another possibility is the kind of "area gates" that Valve's Source Engine uses that associate all objects in a particular area with a common bounding box. In that situation, the Goomba and all of the blocks in the pit load in at the same time, or something close to it. And a third possibility occurred to me while I was making dinner just before writing this, as the phrase "the way Unity does it" floated through my head, which is not to bother with all this "spawn/despawn" nonsense, and instead have an "enabled/disabled" switch on Entities, wherein Components associated with an Entity that's marked as disabled won't be returned in queries targeting those components. All entities start disabled, and are selectively enabled as the window crosses over them. (This suggests the existence of a component query that can disregard the enabled/disabled flag.) We still save on the computation involved in any Component that isn't actually in play, but this also means that far more objects will be in memory at the same time, and Component queries will mostly consist of scrolling past disabled entities. That sounds... not great, performance-wise, but I genuinely don't know how many entities I'm actually going to have in a single level.

    Napkin math! Let's say that we're using 32-pixel tiles. According to Microsoft Paint, this map of Ice Cap Zone Act 2 built with the original Sonic 3 sprites is 16832 pixels wide and 3072 pixels tall, which is 526x96 in tile scale. If we utterly saturate the map with one-tile objects, that's 50496 entities. I have no idea how to feel about that number. That feels like a large number of things to 95% disregard on each individual query, but that also sounds like a small number to a computer.

    This is going to take more thought than I want to put into it tonight. My goal for this week is to think about ↑↑↑ all that stuff.

    My favorite musical instrument is the air-raid siren.
    dipuc4lifeKoopahTroopahIzzimachRoyceSraphimMrBlarneyGlalCornucopiistLilnoobsDisruptedCapitalist
  • CornucopiistCornucopiist Registered User regular
    Kupi wrote: »

    I'm considering multiple solutions to this. The first one that occurred to me was to have two different level scrolling windows, one of which is further out from the camera and spawns in "passive" objects like terrain, while the other is closer and spawns "active" objects like enemies. That way I can be reasonably certain that solid objects on which moving objects rely will be loaded before the moving things need them.

    That just reduces your problem to any system depending only on passive objects. Any 'active' interaction (say a goomba bucket chain) still won't work properly.
    Kupi wrote: »
    Another possibility is the kind of "area gates" that Valve's Source Engine uses that associate all objects in a particular area with a common bounding box. In that situation, the Goomba and all of the blocks in the pit load in at the same time, or something close to it.

    This is again only a reduction but it makes more sense. If you never design a completely open world level (say with a group of wandering traders that really exchange items across the map) you won't have issues.

    Kupi wrote: »
    And a third possibility occurred to me while I was making dinner just before writing this, as the phrase "the way Unity does it" floated through my head...
    ...This is going to take more thought than I want to put into it tonight. My goal for this week is to think about ↑↑↑ all that stuff.

    This is a bit of a mix and I'm pretty sure I don't understand all the ramifications.
    Unity does allow you to spawn ridiculous amounts of entities and then only enable the render component of any that are in your view window (or by distance to camera) . However they were not done porting their physics to DOTS (ex-ECS) it seems until like literally a month ago. I've not implemented it yet so no idea if physics are turned off in the distance or not. I will say one thing; Unitys non-DOTS physics will laugh at your 50496 entities even on a phone. 50496 is for babies! So I'm wondering what functions you're trying to optimize here?

  • IzzimachIzzimach Fighter/Mage/Chef Registered User regular

    Ok, this week I roughed out a little intro area with three starting animals to choose from: Bear, Badger, and Raccoon. They are the glowing blobs surrounded by particles off in the distance. Meanwhile I'm trying to talk to this winged man-lion (a Lamassu)

    bmg6pzn467fb.jpg

    I have a dialog system with the standard "vertical list of responses" layout that so many games use. It works so far but the default UE4 text box only supports one font and no inline bitmaps. I'm going to investigate using the Rich Text widget instead.

    6dy25hekiwb6.jpg

  • KupiKupi Registered User regular
    edited November 23
    Kupi wrote: »

    I'm considering multiple solutions to this. The first one that occurred to me was to have two different level scrolling windows, one of which is further out from the camera and spawns in "passive" objects like terrain, while the other is closer and spawns "active" objects like enemies. That way I can be reasonably certain that solid objects on which moving objects rely will be loaded before the moving things need them.

    That just reduces your problem to any system depending only on passive objects. Any 'active' interaction (say a goomba bucket chain) still won't work properly.
    Kupi wrote: »
    Another possibility is the kind of "area gates" that Valve's Source Engine uses that associate all objects in a particular area with a common bounding box. In that situation, the Goomba and all of the blocks in the pit load in at the same time, or something close to it.

    This is again only a reduction but it makes more sense. If you never design a completely open world level (say with a group of wandering traders that really exchange items across the map) you won't have issues.

    Kupi wrote: »
    And a third possibility occurred to me while I was making dinner just before writing this, as the phrase "the way Unity does it" floated through my head...
    ...This is going to take more thought than I want to put into it tonight. My goal for this week is to think about ↑↑↑ all that stuff.

    This is a bit of a mix and I'm pretty sure I don't understand all the ramifications.
    Unity does allow you to spawn ridiculous amounts of entities and then only enable the render component of any that are in your view window (or by distance to camera) . However they were not done porting their physics to DOTS (ex-ECS) it seems until like literally a month ago. I've not implemented it yet so no idea if physics are turned off in the distance or not. I will say one thing; Unitys non-DOTS physics will laugh at your 50496 entities even on a phone. 50496 is for babies! So I'm wondering what functions you're trying to optimize here?

    There are two things I'm trying to accomplish:

    1) Maintain performance by continually operating on small data sets. Yes yes premature optimization blah blah, but doing it the right way the first time lets you write your program one time instead of three times. (As a reminder, I'm building my own ECS architecture on top of MonoGame, not working with Unity.)
    1a) I am admittedly probably excessively skittish after my collision detection engine gave me frame-skips because I did an assignment through an array accessor (which is the most bizarre performance hiccup I've ever seen).
    2) Stabilize both the act of designing and playing the game's levels by making individual segments of the game react predictably when they're approached the same way, which is harder to do when everything is moving from the start of play.

    ***

    Kupi's Weekly Friday Status Report

    Can you load into play from a level file yet?!

    Not for want of trying! Somewhere around Wednesday evening I realized that all the design work I was doing on the level scrolling system had absolutely nothing to do with my stated goal of loading into gameplay from a level file, so I drew up a "rush plan" to get that done in my Thursday evening. I changed everything in my test project to load globally and spent several hours debugging random small things that had fallen out of alignment, until I hit the big showstopper-- a while back I decided to revise how I was handling my camera logic (using a transform matrix rather than applying the camera's position as an offset to the position each sprite was drawn at. As a consequence, I have no idea where my sprites are. They could be just offscreen. They could be somewhere in orbit around Pluto. They could be exactly where they're supposed to be except flipped over the y-axis because XNA uses CRT pixel coordinates instead of the way any reasonable human being considers how positional coordinates are supposed to work.

    The data is all there, things are happening the way they're supposed to, but I can't see it happening, so no points for this week. If and only if I can find where I put Sonic wagging his finger back and forth will I move on to trying to figure out if my current design for level scrolling is actually worth pursuing.

    UPDATE: After having just a teeny little anxiety attack that I worked through with the help of a friend, I did some early-morning coding and got the sprites to show up:

    eob9j5ojw1e4.png

    Sonic is completely out of position and those blocks are supposed to be on the ground instead of the air, but fucking whatever, man, the sprites are there. I can load into gameplay from program start.

    Kupi on
    My favorite musical instrument is the air-raid siren.
    Lilnoobs
  • HandkorHandkor Registered User regular
    Epic was doing their 5th annual Megajam and again this year I participated.
    I had some anime SFX that I'd been wanted to use for something and figured this was a good time. Too bad I ran a bit shot on time and did have time to actually implement all the attacks like the rocket punch, sword and shield.

    Here is a video of my son playing the game

    ElvenshaeIanatoreelektrikIzzimachRoyceSraphimDaenris
  • eelektrikeelektrik Registered User regular
    Week 9 of my senior capstone game project. Coming to the end of the first 10 week quarter, and have another 10 weeks to work on it in winter quarter.

    steam_sig.png
    KupiHandkordipuc4lifeElvenshaeSmrtnikIzzimachCornucopiistRoyceSraphimDaenris
  • KupiKupi Registered User regular
    Kupi's Weekly Friday Status Report

    Duly chastened by Cornucopiist's frame-challenge of my level scrolling system, this week I set out to create a stress-test for my architecture and see if all that goomba moistening business was really necessary. The results were... not encouraging.

    With the same thoughtlessness that got me into this mess originally, I went with the first idea I had, which was to create randomized mazes that little ants would bounce around it, which would provide a nice dense saturation of entities trying to collide with one another without being unnaturally dense (like, placing all the matter in the universe in the same gameplay tile would not be a fair test of the system's capabilities). I managed to knock the maze generation out in an hour using "Prim's algorithm", which is basically "until you run out of walls, pick a random tile already in the maze and knock out one of its walls that doesn't connect to a tile already in the maze until you run out of walls that qualify". The result looks something like this:

    7wxuldh02s0l.png

    I discovered two things when I placed one ant in each tile that didn't have a wall tile in it:

    1) The engine managed about 12 FPS.
    2) I made a hilarious blunder in the depenetration phase of the collision and movement system.

    The second one is more fun to talk about, so let's go into that first. While I do my level best to keep collision volumes with a "blocking" interaction type from intersecting, mathematical tricks and forces beyond the collision system's control can conspire to leave them overlapping. There are various strategies for dealing with this; the one I've chosen is to shift one or both of the volumes involved in an illegal overlap by a few pixels until either they don't overlap any more or we hit an iteration cap, at which point one or both of the volumes involved is marked as "crushed" and stops participating in further collision for that frame. This movement, like other blocking interactions, is subject to what I've called "push priority". Rather than use a mass-based physics system (which is above my pay grade), I've gone with a simpler concept of weight where the object with a higher push priority pushes around those with lower ones. If a gnat and a shipping tanker collide in my engine and the gnat has a push priority of 2 and the tanker has a push priority of 1, the tanker gets pushed backward. There is one special rule involved, which is that entities without an associated velocity have an effectively infinite push priority. Whatever their defined push priority is ignored; if they lack a velocity, they win.

    ... well, they win during the main physics phase. It turns out that I forgot to implement the "infinite push priority for no velocity" rule in the depenetration phase. So it's possible to shift a completely immobile object by slamming into it with enough velocity, meaning every time my ants bounce off of a wall, they move by one pixel. Given enough time...

    as6rlv8cih3d.png

    ... the ants dig their way out. Appropriate.

    As for the chugging framerate, I've discovered and fixed a number of performance blunders like using a really bad collision test for one particular combination of volume types and completely neglecting to perform a bounding box test before doing more detailed collision, which wasn't quite as catastrophic as it sounds because I was already subdividing the problem space significantly, but there was still a visible improvement by adding the bounding box text to individual volume pair checks.

    My goal for this week is to keep knocking down tall poppies until I either get an acceptable framerate back or I hit the New Year and declare 2019 yet another wasted year of my life, give up, and go back to either Unity or Unreal as long as it takes to get frustrated with them and move to Minnesota to live as a hermit.

    My favorite musical instrument is the air-raid siren.
    templewulfIanatoreelektrikLilnoobsHandkorDisruptedCapitalist
  • CornucopiistCornucopiist Registered User regular
    Kupi wrote: »
    Kupi's Weekly Friday Status Report
    As for the chugging framerate, I've discovered and fixed a number of performance blunders like using a really bad collision test for one particular combination of volume types and completely neglecting to perform a bounding box test before doing more detailed collision, which wasn't quite as catastrophic as it sounds because I was already subdividing the problem space significantly, but there was still a visible improvement by adding the bounding box text to individual volume pair checks.

    My goal for this week is to keep knocking down tall poppies until I either get an acceptable framerate back or I hit the New Year and declare 2019 yet another wasted year of my life, give up, and go back to either Unity or Unreal as long as it takes to get frustrated with them and move to Minnesota to live as a hermit.

    So, one reason why I'm rolling my own landscape generator (now nearly featuring A*!) is that I wanted to have arrays (coming down from lists which are supposedly faster but that's another topic) of stuff like for example blocks. Loads of blocks!
    Partially this is so I wouldn't have to do physics at all except for anything directly around the player, but still have things happening at various levels of granularity. Though of course Unity has crazy fast physics to I've yet to see if my concept/implementation is going to be faster.
    Would it be useful to have good-enough grid based interactions (with perhaps some randomizing thrown in for certain cases i.e. slopes and irregular edges and goomba interaction) across your whole map, and physics only where it's (nearly) visible and you need better shape/edge detection?
    The roaming goombas etc all seem to be plausibly possible to simulate. Especially in an ECS system where you operate on entities anyway- oh my god I'm going to create Conways infinite rule set aren't I?



  • eelektrikeelektrik Registered User regular
    Another progress build video, this is the week before finals week. Still more we could add or fix before we present next Wednesday to end this quarter but I am happy with the progress so far.

    steam_sig.png
    Cornucopiistdipuc4lifeDaenrisIzzimach
  • CornucopiistCornucopiist Registered User regular
    habxph0qo3hp.png

    #Gamedev
    Finally got A* to work in the RPG project... for the moment this is just a diversion. I'd love to use this to lay pipelines and monorails and whatnot in my FNS cityscape and later on Turbinehead), but I'm far from that sort of stuff, and I want to focus FNS development to get gameplay in and release early Q2 2020.
    Probably the first part of this that will see the light of day is a follow-up to ChooChoo after FNS is done.
    Still, it's a good tool to get under my belt, and a few things I picked up are probably going to still get into FNS. Vector2Int, for one, which I can't believe no tutorial uses, but also using my own classes combining Vector2Ints with ints for terrain type etc.
    The cities are really too regularly distributed at the moment, so the next step is probably going to be to figure out how to get them to seek and settle on the closest geographical features such as lakesides, river outlets,...
    After that I have to look into city generation. I have a neat set of functions that make nice labyrinths, but they're a bit too random for city gen.

    IzzimachLilnoobs
  • IzzimachIzzimach Fighter/Mage/Chef Registered User regular

    So I got the Twine import kind of working. I exported the Twine data as JSON and imported it into Unreal Engine as a data table. That wasn't too bad.

    The hard part was parsing the Twine "code" with macro and such and ALSO using the RichTextBlock in UE4 to display the text. There is a huge pile of C++ macros and templates to wade through. But at long last, I made it so that clicking on a link calls a blueprint function, yay!

    Below is the text in Twine and the corresponding display in a UE4 RichTextBlock. Not sure if I will keep the link inline with text or just shove them all to the bottom.

    kclmavlazbyj.png

    dipuc4lifeLilnoobsCornucopiist
  • KupiKupi Registered User regular
    edited December 7
    Kupi's Weekly Friday Status Report

    It seems like every week I piss and moan about how ugh it was such a bad week I'm so sorry everyone, but this week, oh man, I fixed so much broken stuff aaaaaaaand the performance is still nowhere near acceptable but we're gonna start out with the good news.

    For a while I've been noticing that the memory profiler would log a garbage collection about every ten seconds or so. Nothing I could actually see affecting the framerate, so I let it slide, figuring that it was just an incidental GC that happened because the runtime demands that GCs happen every so often no matter what. (That I believed this is in direct contradiction to the truth that I'd already internalized that every extant CLR GCs on allocations only is an embarrassment.) Well, when I ramped my stress test up to the next order of magnitude (a 100x100 maze instead of a 20x20 maze), all of a sudden the garbage collector went berserk. But, weirdly, it only went berserk with a very specific combination of systems active: collision detection, sprite animation, assign-sprite-from-animation-to-sprite-displaying-component, and make-ants-that-hit-walls-bounce. Only if all four were in play. Remove any one of them, and the GC went back to every ten seconds or so. I also determined that if I removed two very specific lines from the sprite animation system (the simplest one to use individually), I could get the GC to stop running entirely. I had a bit of a despair attack when I realized that the "only" thing those two methods had in common was the use of the typeof() operator. And if I can't use typeof() without making garbage, then the whole system falls apart and that "hermit in Minnesota" plan starts looking really attractive. BUT! "Select() isn't broken", as they say, and while typeof() isn't contractually obligated to give you a cached instance of a Type object every time you run it, allocating a new instance each time is performance stupid in a way that the CLR engineers aren't prone to being, so that couldn't have been the problem.

    At a loss, I reached deep into the well of black magic and went and got a tool called the CLR Profiler, which is one of those things a Microsoft engineer wrote because he had to chase some kind of infuriating obscure memory leak, realized it would be useful to other people, and published it, and then Microsoft stopped supporting it because ¯\_(ツ)_/¯. But it still works, and it hooks into the CLR and logs every goddamned memory allocation and function call that happens for the duration of the program. It took a bit of doing to move my exact situation into a form that the CLR Profiler would accept, but I ultimately got a histogram out of it. 3 kb of integer buffers, 7 kb of Tile instances, 5 kb of query tracking objects... and 25 megabytes of GSTypes.

    Found the issue.

    Now, I've said before that I used Newtonsoft.Json for my serialization purposes. JSON restricts you to basic data types for dictionary keys: integers, floats(?), and strings. So when you serialize a dictionary that uses a non-basic type as its key, Newtonsoft.Json uses the object's ToString() method to generate its string representation. That's... not great for my purposes, because I use Types as keys and those expand to a whole bunch of information in their ToString(). So I wrote a tiny wrapper struct for Type called GSType (the "GS" derives from my game studio name) that emits much smaller type names when serialized. I created an implicit conversion from Type to GSType and vice-versa because calling the constructor every time you go back and forth is a pain. I'm not sure why or how GSType instances got boxed or if the CLR just decided to stick them on the heap, but either way a bunch of low-level methods down in my tight loops were creating garbage, and it only reached the level where the garbage collector really got affected once four or five systems were issuing queries and antagonizing the things that used GSTypes heavily. Fortunately, I was able to gently re-tune my approach to serialization and bypass the need for GSType entirely.

    From there, I've been doing a whole bunch of performance-tuning changes. For the purposes of CPU tracking I've turned to using Visual Studio's CPU profiler, because it turns out that the amateur benchmarking library I wrote to get something off the ground quickly (and helped me diagnose a bizarre performance hit associated with a single line) will measure itself if you use it too broadly, which led me down a rabbit-trail where every time I tried to figure out why this one function was being such a performance sink, it got worse.

    Unfortunately, one of the major hotspots I discovered was iteration time in my "query cursors". The short version of this is the way you get a stream of Entities (which is actually just multiple streams of Components delivered in parallel) is you put them all in a Query object and hand that Query object a ComponentStorage to query against. Then, every time you turn the crank on the Query, each Cursor gets a new value, and any changes you made to the last value it held get queued to be written back to the ComponentStorage when the query completes. This involves a whole bunch of individual struct value copies, which I've discovered C# doesn't like at all. If you want to get a lot of data from one array that you don't want to write back onto until later, the most efficient way to do it is to copy everything at once into another buffer using Array.Copy(), which translates into the C intrinsic memcpy(), which you really aren't going to get faster than. So I went ahead and finished the work I started on being able to run a Query using Component "Buffers" rather than Cursors; now you can do a query that returns every result at the same time. This consumes more memory, but right now I'm not constrained on memory, I'm constrained on CPU, so it's a worthy trade.

    During the unit-testing of my buffered query system, I discovered that the code that finds the "archetypes" (combinations of Components) that qualify to be returned by a Query had a serious bug that could cause entire Archetypes to just never be returned. Whoopsy-doodle! I replaced that code with code that doesn't fail to do what it's supposed to. The old code is in a better place and will always be remembered in the Git commit history just beneath the log entry disparaging it for being terrible.

    The result of all of this perf work is... my current stress-test still clocks an average frame time around 20 ms, which is still over-budget and especially embarrassing because I took the ants out of the maze, which means nothing's moving. I'm spending 20 ms of CPU time to track the action of a bunch of completely immobile blocks. Fortunately I've already identified a few spots where I can avoid recalculating values that I know in advance aren't going to change, but damn.

    ADDENDUM: At 11 PM last night I finally realized what was up with GSType. GSType is a struct, but its one value was a heap-managed reference type. So GSType has to have a reference handle in the garbage collector that keeps the Type instance it wraps alive. And every time you make a new handle, you potentially trigger a GC.

    Kupi on
    My favorite musical instrument is the air-raid siren.
    JusticeLilnoobs
  • eelektrikeelektrik Registered User regular
    Working on improving movement in our game, and made a short demo with some different versions of our d20 rolling around. Threw it into a google form and any feedback would be appreciated

    https://forms.gle/2xsG2SiqqrAHRUZd8

    steam_sig.png
    Elvenshae
  • CornucopiistCornucopiist Registered User regular
    Kupi wrote: »
    Kupi's Weekly Friday Status Report

    I'm not sure why or how GSType instances got boxed or if the CLR just decided to stick them on the heap, but either way a bunch of low-level methods down in my tight loops were creating garbage, and it only reached the level where the garbage collector really got affected once four or five systems were issuing queries and antagonizing the things that used GSTypes heavily. Fortunately, I was able to gently re-tune my approach to serialization and bypass the need for GSType entirely.

    Now I feel insecure. How often should I be serializing? Am I writing to JSON enough?

    Chr*st, man, I'm only doing that stuff when the user hits the *save* button... am I wrong?!

Sign In or Register to comment.