Jump to content
  • Advertisement


  • Content Count

  • Joined

  • Last visited

Community Reputation

1589 Excellent

About snake5

  • Rank

Personal Information

  • Interests

Recent Profile Visitors

The recent visitors block is disabled and is not being shown to other users.

  1. snake5

    OOP is dead, long live OOP

    Close, but not quite. This is from the perspective of the user of an abstract interface, whereas what I'm thinking about is like, for example (hypothetical), there are two polygon-polygon intersection tests, one is faster but "rounds the corners" a bit, and the other is exact, and the dependency on the other one would come from a game situation where a projectile was moving near a corner, where the algorithm would be expected to miss the intersection, so it could not be replaced without destabilizing the balance of the game. It is something of a butterfly effect, and somewhat related to bugs turning into features after shipping. I guess I've managed to create the wrong impression there. I am not looking for such advice, it tends to find me itself all the time, which does not seem like how things should be. That said, I don't want to make this about me, it just seemed like once again here comes someone with the one true way to do things. Dogma is merely an authoritative principle. Which means it just takes someone (a famous computer scientist / programmer) or something (Wikipedia) to "authorize" it, or make it trusted, and by that definition all CS "principles" I've encountered are really dogma (although the difference isn't that huge to begin with). And principle is defined as fundamental truth or assumption, which again I would argue these examples are not.
  2. snake5

    OOP is dead, long live OOP

    I totally agree, though what I consider to be subjective voodoo are the relationships and structure of objects themselves. This seems to appear the most in classes that are effectively layers of managers, proxies and factories. The question I ask is, should you split that class? I don't question the ability, as you have defined it, but the productivity of always doing so. Say you have a "human" component with "health" member. Does it make sense to move "health" to a separate component? Even if there is no other component that would need it? Likewise, multiply-add are clearly two separate operations, but are joined for performance reasons. And a lot of good that did for OpenGL... as a particular counterexample, I see AZDO which is almost a self-sufficient subset of the broader, bloated API, which makes it pretty hard to argue that it was merely an extension. STL is for the most part a reasonable example in favor of the principle, though I find that it likely wasn't aimed at STL developers. And it's not like the spec hasn't changed at all over the years. IIRC, string::c_str() was at some point allowed to add the terminating zero at runtime and range-for was changed to allow for a separate end type. Sure, I'm not against separation of software layers, but again, how does that make it a principle? I'm not sure if there's a term for it, but there is a kind of dependency that is not explicitly written, but when a part is substituted for another with a fully compliant interface, something subtly breaks down regardless. Seems to be particularly common with computational geometry and image processing. Also observed with LLVM when manually specifying optimization passes to run. My point here is that no interface would help in this case, and making one only would cause performance issues (whether compile-time from templates or runtime from virtual interfaces), so how can something that doesn't always work be a principle? I agree, but like I said, the issue here stems from practical considerations. When writing a game from scratch, all in the same code base and language, without data-driving anything, all such issues can probably be avoided quite easily, otherwise I'm not too sure. All in all, my main point regarding SOLID(C) is that if some theory does not work always, without exceptions and provably so, it does not deserve to be a principle, and should not be applied unquestioningly. And this of course applies to the omnipresent horrible kind of OOP as well. Please don't get me wrong, I did notice it and appreciate that a lot. I'm just saying that in my opinion, it has a great deal more value than all the criticism of original code, and so there should be even more code changes, metrics and reasoning behind the code changes.
  3. snake5

    OOP is dead, long live OOP

    After reading most of the article, I felt I had two points to make: 1. I agree that object oriented design doesn't have to be as bad as it usually turns out, and DOD overlaps heavily with relational database theory. However, I think that the most important distinction here is that DOD asks that data (not code) is considered first, whereas OOP assumes data joined with and secondary to its code and asks you to compose them in some subjectively pleasing way. Ultimately, code is the tool that transforms data, and not the other way around. That said, it is also a tool for communicating processes to various audiences, so I'm waiting for the day that we'll stop thinking in extreme principles and find a way to reconcile both (and possibly other) purposes so that they work together to the extent possible. 2. Can we finally drop SOLID(C) please? Should be obvious by now that some of the rules are vague and infeasible at best and self-serving at worst. SRP - what is "single"? Is "game" single? Is manager single? Entity, vector, float, byte? Tree, node, part of node? Is multiply+add not single, and what about SIMD? Where do we vote about all of these? Principles that mean nothing specific are only used as a tool to justify screaming about how poor someone else's code is, regardless of whether that is actually the case. O/CP - in my experience, when the data changes, the interfaces change as well (for optimal access). And similarly, I find implementations to be extremely difficult to substitute without side effects, since they themselves have often very different I/O constraints, performance characteristics and side effects. So I'm not sure in what fairy tale world is interface lockdown or trying to accomodate unforeseen implementations productive. LSP - in general, there's nothing to object to and people generally try to do this. That said, interfaces are frequently used to bridge inherently incompatible constructs (such as different rendering backends) where support for certain features is limited and it would be pointless to go to extremes to provide perfect feature parity. ISP - no objections here. Note that this is the only principle for which you have yourself provided a practical argument to support it. DIP - interfaces exist to bridge multiple implementations, otherwise implementations could be used directly, which is also beneficial for performance due to inlining and lack of virtual calls, and greatly simplifies allocation of data. You say that a POD struct for communication is enough but how is a POD struct different from a regular function parameter list? And I can almost guarantee that a plain function won't be considered DIP-compliant by many people. I would argue that it's merely a somewhat obvious tool used to accomodate refactoring, definitely not a principle. CRP - while seemingly nice in theory, there is the practical consideration of C++ multiple inheritance casting headaches as well as the issue of composing components that need to know about their parent objects or neighbors. Inheritance, while not without its numerous downsides, solves the parent object issue without extra pointers. Since you only mention SOLID(C) in your code criticism, and not design, I would argue that the worth of most of these is limited to justifying being annoyed about someone's code, unlike a concrete code example, which also shows an alternative implementation, open to concrete and fact-based comparisons, reviews and future improvements.
  4. snake5

    SGScript 1.4.0 has been released!

    In under 3 weeks, SGScript bindings for .NET 2.0+ were finished and now it's time to release v1.0.   Only Windows is currently tested and supported (both standalone and running under Unity) but I'll be looking into options to support Linux and macOS as well.   The API is made to look similar to the C API where possible which I hope makes up for the missing documentation.   The binaries for SGS.NET are included in the Windows version of 1.4.1 binaries.   Probably currently the biggest missing thing is not being able to bind arbitrary objects to the engine, only IObject-based ones, the next version will offer more binding options. I'm also not entirely sure how the native library linkage works within other engines/platforms so that's probably another thing to work on in the upcoming version.   Here's a screenshot of SGS.NET running in Unity
  5. snake5

    SGScript 1.4.0 has been released!

    Yes, that's pretty much what it is, something between the two. More built-in features than Lua, faster than the default Python implementation.   Those are just the terms of the MIT license. Lua uses the same exact license, by the way. :)   That's interesting, I did not realize this could be off-putting to some. The reason I included it is that there are non-free scripting engines (e.g. SkookumScript) and it seemed like an important thing to mention. Besides, it's nothing original - the About page of the Lua website features a similar section at the bottom.   Anyway, thanks for your input, I'll try to think of a way to improve the text on the website.
  6. Hi, I'm the developer of SGScript and I just wanted to let you know that I've been working on the project all this time, and there have been lots of improvements since my previous post. Today I have released SGScript 1.4.0. Some highlights from the Change Log since 0.9.5: added class syntax added 'defer' keyword (moves statement to end of block, 2+ statements are executed in opposite order) SGSON serialization (JSON-like, with function syntax) added coroutines, thread syntax and API (thread, subthread, sync, race, end_on) added map literal (map{ [5] = true }) symbol (named persistent variable) & coroutine serialization null-coalescing operators (??, ??=) single header implementation Some useful links: Try the language at sgscript.org SGScript on Discord -------------------------------- Now that that's out of the way, I have a question: Seeing as many people these days don't make their own engines, is there any popular small to medium-sized open-source engine that you use for which you'd like to use a scripting language like this one?
  7. It does handle mipmaps. Every mipmap is retrieved separately by dds_seek/dds_read. dds_read_all is only a wrapper that copies everything into one big buffer with a predefined order of items.   Basically whatever texture part you want out of it, you can move the pointer to it using "dds_seek( &info, cubemap_side, mipmap_id )" and then memcpy it using "dds_read( &info, my_buffer )"   What it doesn't handle though, is pitch (row length in bytes). If your target buffer expects different pitch, it will be necessary to use an intermediate buffer in size that can be retrieved using dds_getinfo (size member of dds_image_info output struct). Or write your own reading function (once you have the side/mipmap offset, it shouldn't be that hard).   There actually are multiple DDS formats, one pre-DX10, the other one contains an extra header. It even supports partial cubemaps and all sorts of weird pixel compositions. Nobody needs them (not that I recall, anyway) but it's important to detect these cases to avoid buffer overruns and broken visuals. Sadly, the format isn't quite as simple as one would expect.   Internally I use something even more simple (this is the header, pixel sorting order major->minor is - side, mipmap, depth, height, width): struct TextureInfo /* 12 bytes */ { uint8_t type; /* 2D[1]/CUBE[2]/VOLUME[3] */ uint8_t mipcount; uint16_t width; uint16_t height; uint16_t depth; uint16_t format; /* format ID (rgba8/r5g6b5/dxt1/dxt3/dxt5/..) */ uint16_t flags; /* SRGB[0x01]/HASMIPS[0x02]/LERP[0x04]/CLAMP_X[0x10]/CLAMP_Y[0x20] */ }; struct Texture { char magic[4]; /* STX\0 */ uint32_t datasize; /* size of 'data' */ TextureInfo info; uint8_t data[ datasize ]; };
  8. If you only need minimal parsing (to map byte offsets to cubemap sides/mipmaps for some common color formats), this might help:   https://github.com/snake5/sgs-sdl/blob/master/src/dds.h https://github.com/snake5/sgs-sdl/blob/master/src/dds.c Usage example: https://github.com/snake5/sgs-sdl/blob/master/src/ss3d_engine.c#L745
  9. Just one thing that wasn't mentioned yet: in C++, "public:" / "private:" / "protected:" labels define different blocks of variables. Within these blocks, the order is preserved, however the standard doesn't guarantee that block order will be preserved. I've no idea how often reordering is the case in practice, though simply using structs / making all members of a class public will guarantee that there are no ordering issues.
  10.   Would be nice (and often necessary in the case of deferred rendering) but not important considering all of the other aspects of a game engine. But if you must...   First of all, all of that inheritance stuff is completely unnecessary. Also, shaders will be structured in an entirely different way, and same features will often require different approaches for the rendered images from both renderers to be equal - a basic deferred renderer probably won't have support for multiple materials, for example, so to add that, a "material ID" would have to be stored in the G-buffer (possibly the stencil buffer), and used as a pixel filter for multipass light rendering.   Some simple observations: The forward rendering pipeline requires a "for each item {  render(item,ambient_lighting);  for each light {  render(item,light)  }  }" rendering order or similar The deferred rendering pipeline requires a "for each item {  render(item)  }  for each light {  applyLight(light)  }" rendering order The details vary but the point remains - you need a way to render all of the visible (or just all) items. Also, you need to render polygons that roughly describe the area occupied by the light for deferred shading.   So, you can achieve a lot with just two functions - DrawItems(items) and DrawTriangles(vertices) - used with the appropriate shaders and render targets at the right time. How you choose to call these functions is completely up to you. Might be as simple as RenderForward() and RenderDeferred(), a configuration variable or something else entirely.   Also, to start with simple code, you can just draw a fullscreen quad for each light - it will be slower (a lot slower with many lights) but guarantees effortless accuracy.   There are lots of optimizations/improvements to apply here (item ordering by transparency, depth sorting, global sorting with item span rendering, separate solid/transparent forward rendering passes, Z prepass etc.) but I'll leave those unexplained for now, since they're not really relevant for a school project type engine. You can always ask more questions to Google or in the forum when you're done implementing the basics.
  11.   They have the always_inline attribute, which FORCEINLINE is defined to in UE4 as well (specifically, #define FORCEINLINE inline __attribute__ ((always_inline))).   As for usage, I use it to enable inlining of trivial (single-line) functions in debug builds, to improve performance without sacrificing much of the debuggability.
  12. snake5

    Favor Small Components in Unity

    The post is obnoxiously one-sided. I can easily see the counterarguments to this idea: Increased number of memory allocations leads to reduced performance. Amount of glue code increases due to everything being separated, which in turn also reduces perfomance and increases the complexity/unreadability of the code. The amount of files to deal with while editing any object is increased. Any one-off behavioral changes for certain object types require yet another file to store the derived component, thus splitting the code for special objects for many files, making editing more complicated. Because of this, I am highly skeptical of the claim that "your entire project is easier to manage and the complexity of the project can be manageable again". Also, I see no "before" and insufficient "after" (where's the glue code?) code examples to illustrate your point and help with the credibility of your claims. There is no data (and I highly doubt there will be) that could prove your point. This is fine for a blog/forum post, not for an article.
  13. snake5

    Lerp vs fastLerp

    Depends on the case. With the fast version, lerp(a,b,1) may not return b. Multiplication by 0/1 are generally not lossy operations, addition and subtraction of non-zero numbers can easily be lossy.   So, fast lerp works fine almost everywhere but if you absolutely need lerp(a,b,1) to return b, use the slightly longer version.
  • Advertisement

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!