• Content count

  • Joined

  • Last visited

Community Reputation

3902 Excellent

About TheComet

  • Rank
    Advanced Member

Personal Information

  • Interests


  • Github
  • Twitch
  1. Manual syntax trees

    I thought this thing I wrote might be a worthy candidate for a coding horror. Expression* SymbolicMatrix::determinant3x3() const { /* * Given the matrix * * / a b c \ * A = | d e f | * \ g h i / * * The determinant can be calculated with: * * det(A) = (aei + bfg + cdh) - (gec + hfa + idb) * * The entries in the matrix are stored such that a=0, b=1, c=2, etc. */ return Expression::make(op::sub, // (aei + bfg + cdh) - (gec + hfa + idb) Expression::make(op::add, // aei + bfg + cdh Expression::make(op::add, // aei + bfg Expression::make(op::mul, // aei Expression::make(op::mul, // ae entries_[0]->clone(), // a entries_[4]->clone()), // e entries_[8]->clone()), // i Expression::make(op::mul, // bfg Expression::make(op::mul, // bf entries_[1]->clone(), // b entries_[5]->clone()), // f entries_[6]->clone())), // g Expression::make(op::mul, // cdh Expression::make(op::mul, // cd entries_[2]->clone(), // c entries_[3]->clone()), // d entries_[2*3+1]->clone())), // h Expression::make(op::add, // gec + hfa + idb Expression::make(op::add, // gec + hfa Expression::make(op::mul, // gec Expression::make(op::mul, // ge entries_[6]->clone(), // g entries_[4]->clone()), // e entries_[2]->clone()), // c Expression::make(op::mul, // hfa Expression::make(op::mul, // hf entries_[7]->clone(), // h entries_[5]->clone()), // f entries_[0]->clone())), // a Expression::make(op::mul, // idb Expression::make(op::mul, // id entries_[8]->clone(), // i entries_[3]->clone()), // d entries_[1]->clone()))); // b } All jokes aside, how would one make this look better?
  2. I'd add methods for doing that to you serializer/deserializer classes: class Deserializer { /* ... */ void readFloatArray(std::vector<float>* v) { size_t arraySize = readUInt(); // we saved the size so we know how much to read v->resize(arraySize); for (size_t i = 0; i < arraySize; ++i) v->push_back(readFloat()); } /* ... */ }; class Serializer { /* ... */ void writeFloatArray(const std::vector<float>& v) { writeUInt(v.size()); // save size, for reading back for (size_t i = 0; i < v.size(); ++i) writeFloat(v[i]); } /* ... */ }; Then, to read the 100 floats: File file;"whatever.dat"); std::vector<float> myFloats; file.readFloatArray(&myFloats);
  3. For some reason the compiler cannot resolve which base class set() is to be called (someone more experienced might be able to shed light on the exact issues here). The solution seems to be to do something like this: template <class ...Ts> class entity : public component<Ts>... { public: entity() {}; ~entity() {}; template <class U> void set(const U& data) { component<U>::set(data); } };
  4. A bit late to the party, but have a look at this pattern, I think it's a really elegant way to serialize binary data. Also have a read of section 7.4 in Beej's guide to network programming about data serialization and how to make binary data portable here: struct Serializer { // To be implemented by files, buffers, network connections, or whatever stream you want to write to virtual size_t write(void* data, size_t size) = 0; bool writeUByte(unsigned char byte) { return write(&byte, sizeof(byte)) == sizeof(byte); } bool writeUInt(uint32_t i) { return write(&i, sizeof(i)) == sizeof(i); } bool writeFloat(float f) { uint32_t buf = htonf(f); // Convert to portable representation return writeUInt(buf); } bool writeVector3(const Vector3& v) { bool success = true; success &= writeFloat(v.x); success &= writeFloat(v.y); success &= writeFloat(v.z); return success; } // Etc. }; Basically you write methods for all of the different types of data you want to have serialized in your program (maybe you have buffers, strings, quaternions...) Then you can implement the serializer for a file like so: struct File : public Serializer{ size_t write(void* data, size_t size) override { return fwrite(fp_, data, size); } private: FILE* fp_; }; Or you could implement the serializer for a buffer: struct Buffer : public Serializer{ size_t write(void* data, size_t size) override { for (size_t i = 0; i != size; ++i) buffer_.push_back(((char*)data)[i]); // horribly slow way } private: std::vector<char> buffer_; }; You get the idea. The point is, the code writing the data doesn't have to care about what it's writing too and it also doesn't have to care about endianness or type sizes, because that's handled by Serializer. Then, similarly, you write a Deserializer to handle reading the data back: struct Deserializer { virtual size_t read(void* dest, size_t size) = 0; unsigned char readUByte() { unsigned char buf; read(&buf, sizeof(buf)); return buf; } uint32_t readUInt() { uint32_t buf; read(&buf, sizeof(buf)); return buf; } float readFloat() { uint32_t buf = readUInt(); return ntohf(buf); } Vector3 readVector3() { Vector3 v; v.x = readFloat(); v.y = readFloat(); v.z = readFloat(); return v; } }; Example usage would be: struct Vector3 { float x, y, z; }; int main() { Vector3 v = {1.0f, 4.0f, 7.0f}; File ser;"whatever.dat"); ser.writeVector3(v); ser.close(); File des;"whatever.dat"); v = des.readVector3(); des.close(); }
  5. Newbie, Need Help..

    If you were me and saw this thread with 1. An unhelpful title 2. A post that doesn't ask a question How would you respond? It's like going into a car repair shop without a car and saying "I have a problem..." and then just awkwardly standing there. Please ask an actual question next time.
  6. @_Silence_ that would be less than optimal and defeating the purpose of the dirty flag in the first place. In practice, doSomethingThatChangesResult() can be called anywhere from 2 to thousands of times before getResult() is called. With your suggestion, the time consuming calculation would needlessly be performed thousands of times instead of just once.
  7. I've run into this problem a few times in my own code and in code from open source projects and would like to hear your op-onyos about it. It basically looks like this: class Foo { float cachedResult_; bool dirty_; public: void doSomethingThatChangesResult() { dirty_ = true; /* ...*/ } float getResult() { if (dirty_) cachedResult_ = doComplexCalculation(); return cachedResult_; } }; The problem is that you cannot use Foo::getResult in any other methods that are const. As a client of this class, you don't necessarily care about whether the result is cached or not (or maybe you should be caring, and that is the core of this apparent issue?) Is it acceptable to make dirty_ and cachedResult_ mutable in exchange for making getResult() const?
  8. I'm going to go ahead and say yes, there is such a thing. In the brief period of my life where I actually completed a game, I went looking for voice actors on youtube. I was pleased to find an overwhelming number of people who were pretty much unknown (they have 10-100 subscribers or something) and yet their voices sounded amazing. I found a girl to voice my female character for a total price of free, and it sounded really good.
  9. Java game from 1998

    I suggest hopping onto #graal on and asking the guy personally there.
  10. Using DOT to Debug Data Structures

    Hi! It's fairly stable at this point (as in, it doesn't crash, there are no memory leaks, it correctly calculates results in every situation). The biggest API change in the near future will be that positions and rotations are specified in local space rather than in global space (which is currently the case). Other than that I don't see the API changing. Functionally, the way it was designed introduces some major flaws (none of which you will not run into if you don't try to solve nested trees with multiple end effectors). I'm still working on getting those fixed.
  11. It should be pointed out that doing it this way will introduce new, harder to solve design issues into your code that can potentially explode into a huge mess. For instance: Draw order of different entities is no longer defined. Before, you could be certain that no matter how many Cars you added and removed and how many Boats you added and removed, all of your cars would always draw before boats. With the Entity base class, this is no longer the case. It could be that Cars and Boats and Boars and Goats are all interleaved in weird ways and thus rendered/updated as such. Why is this an issue? Well, if you want to put cars on your boats and properly calculate collision for this (for example), while updating one of your car entities, the boat your car is on is in an unknown state. Was the boat already updated? Or is it still waiting to be updated? Half of the time you will be calculating collisions using the previous frame's boat position, the other half of the time you will be using the correctly updated boat position. As a side-note: This is the fundamental flaw with ECS frameworks and why I've taken such a disliking towards them.
  12. How to stay motivated?

    I find the github issue tracker to be a powerful motivation tool, if you use it correctly. Stuffing a three layered cake into your mouth all at once will of course not end well, and the same is true for any programming project: If you try to think about how much you have yet to implement, you will be mentally overwhelmed. The issue tracker helps you break your project down into small manageable steps, and if you're using github, you can generate visual graphs of your progress and use milestones to see the progress of particular features in your game. It's a lot easier to motivate yourself when you can set goals and reach those goals. For instance, you could tell yourself that you will fix 2 issues a day, and when at the end of the day you were able to fix those two issues, you can smile to yourself and feel good about your accomplishment.
  13. How do you balance gaming and game dev?

    Everything has pretty much been said, but I'd like to share an idle observation. I'm fairly active in the GDNet online chat, enough to know pretty much everyone who talks there, and I've noticed that all of the people there who are good at making games are making games. Like, all the time. I've never seen them do anything else (be it gaming or other forms of goofing off). Those that are struggling with improving their coding skills and have been stuck in the same spot for years and seem to be asking the same questions over and over in the chat are those that also play a lot of games in their free time (or are only there for the lolz). It's really not enough to invest only 1-2 hours a day into coding and expect to improve beyond a basic Level. For me: I will immensely enjoy playing the occasional rare game. Half Life 2, Portal, StarCraft 1+2, WarCraft 3, Banjo Kazooie, to Name a few. But I'm not the person who can play games for longer periods of time. Even with the examples I mentioned, I had to turn them off after about an hour and go back to coding. My default mode on the computer is to write code. Everything else I do I consider to be "goofing off".
  14. In this article, I'd like to share a method I used to help visually debug tree-like data structures by leveraging the DOT format and Graphviz. This may be useful to you if you ever end up having to work with low-level data structures and no way to visually see what your code is doing. The Problem During the early stages of development of my inverse kinematics library (which, at the time of writing, is in its alpha stages and not quite ready for general use yet), I was working on an algorithm which takes a scene graph as its input and generates an optimised structure consisting of a tree of "chains", specifically designed for use with an IK solver. The transformation is not that easy to explain in words, but here is an illustration of before (a) and after (b) (please excuse my MSPaint skills; I'm a programmer, not an artist): You can see here that each end effector in the scene graph specifies a "chain length" parameter, which tells the solver how many parent nodes are affected. Since the IK solver works most efficiently on single chains of nodes, it makes sense to break down the scene graph into multiple chains which the solver can then process sequentially. This is illustrated in (b). Notice how chain 1 (red) becomes isolated from the rest of the tree after processing, because its end effector only specified a length of 1. Also notice how in the new structure each chain consists of only a sequence of nodes with no branches. The algorithm had to be able to handle a few weird edge cases, such as: What happens when you place an effector on a node that has multiple children? What happens when there are multiple end effectors in a chain? What happens when an end effector specifies a chain length that doesn't quite join up with the rest of the tree? This of course meant it was harder to test and make sure it was working correctly. I did of course write a suite of unit tests using Google's testing framework, but I wanted more: I wanted to have the ability to visually look at what my algorithm was generating, and I wanted to do this without having to use some fancy 3D engine. Inroducing: DOT and Graphviz DOT is a simple graph description language. Graphviz is a set of open source tools for generating graphics from DOT descriptions. For example, the following DOT code: graph testgraph { a -- b; b -- c; b -- d; } Compiled with dot as follows: dot -Tpdf -o testgraph.pdf Produces the following graphic: DOT is quite a powerful language. It's possible to specify colours, shapes, multiple connections between nodes, and much more! Read up on the format specification for more information. In only a few lines of code I was able to iterate the optimised chain tree and serialise it to DOT. This is what the example tree I drew in MSPaint looks like after it is broken down by my algorithm and exported to DOT: Things to note: The black edges show the connections between the original tree. The red edges show how the chains are connected (just like in the first figure (b)) Effector nodes are coloured blue Nodes that mark the start or end of a chain are square. You can see for example that node 6 is square because it has two child chains, but node 2 is not square because it's in the middle of the second chain. And just like that I had a powerful way to quickly spot errors in my algorithm. Using python and watchdog I wrote a simple script that would monitor the DOT file for changes and automatically compile it to PDF. Thus, every time I ran my program the graphic would update immediately on my second monitor so I could inspect it. Another Example In another application I wrote, I implemented an intrusive profiler which would dynamically build a tree from the callgraph and store timing information in each node. I thought it would be cool to also dump this tree to DOT format and see what it looked like. Note that in this case I didn't use the "dot" command line tool, instead I used "neato" (this is also part of the Graphviz package). Neato has a different layout algorithm based on physical constraints, which in this case produces much nicer graphs than "dot": I find it quite beautiful to look at. What you see here is a visual representation of how much time was spent where in the program. Nodes that are red are "hot" (meaning lots of time was spent in them) and nodes that are blue are "cold" (nearly no time was spent in them). If you zoom in a little bit you can also see that I exported some of the profiler parameters of each function. While this does provide a very nice birds eye view of where your program needs optimising, I would of course recommend using proper profiling tools to further analyse the slow functions. In conclusion Due to the simplicity of the DOT language, it's trivial to write an exporter for it, so if you ever find yourself fiddling with low level data structures, consider dumping them to DOT and visualising them. I found this to be extremely helpful during development of my IK library.
  15. Nobody Wants A Cybergod?

    Why do you do this? You seem to think "the industry" is somehow "us" (wat?) and that "you" are special because you aren't part of "us".... Or some shit. The industry isn't this elite private club that snoots at the uninitiated. Kid, the reason you aren't getting hired by "the industry" is because you have no useful skills. You have no programming experience no game design experience trouble communicating ideas in the English language (do you even read what you type? Every paragraph can be summarised into a single sentence with no loss of information). a stuck up attitude about how you are better than everyone else If you had any value at all, you would easily find a job in "the industry".