GDNet+ Basic
  • Content count

  • Joined

  • Last visited

Community Reputation

7080 Excellent

About Juliean

  • Rank

Personal Information

Recent Profile Visitors

25920 profile views
  1. Ah, the generateSortFunction-idea is just what I was looking for, thanks Unfortunately I really cannot/do not want to use std::function here (aside from its overhead it also disallowes constexpr here too), and while I could simply template the generateSortFunction, it would still require me to store the result as std::function due to the capture. I think I found a "better" solution though: using CompareFunction = bool(*)(const SortData& left, const SortData& right); // make the generateSortFunction templated with the adress of our Sort-function as non-type argument template<CompareFunction Sorter> constexpr auto generateSortFunction(void) { return [](SortingVector& vData) { std::stable_sort(vData.begin(), vData.end(), Sorter); }; } constexpr auto generateSortFunctions(void) { // we can just store the lambda directly as a function-pointer constexpr CompareFunction frontToBack = [](const SortData& left, const SortData& right) { return left.z > right.z; }; constexpr CompareFunction backToFront = [](const SortData& left, const SortData& right) { return left.z < right.z; }; constexpr CompareFunction texture = [](const SortData& left, const SortData& right) { return left.pTexture < right.pTexture; }; using SortFunction = void(*)(SortingVector& vSortData); // now those function-pointers can be used to statically generate different overload of generateSortFunction constexpr std::array<SortFunction, 3> vFunctions = { generateSortFunction<backToFront>(), generateSortFunction<frontToBack>(), generateSortFunction<texture>(), }; return vFunctions; } I'm storing the lambda directly inside a function-pointer and pass that to the generateSortFunction as a non-type template parameter. Wohoo for modern C++ I'll just have to check whether this actually allows the compiler to inline the sorting-function into std::stable_sort, but I'm pretty certain it can.
  2. So while refactoring my code that uses functors into lambdas, I came across a specific function-pointer table used for sorting that I'm struggling to get right. Thats the current code // some functors: struct FrontToBackSorter { inline bool operator()(const SortData& left, const SortData& right) const { return left.z > right.z; } }; struct BackToFrontSorter { inline bool operator()(const SortData& left, const SortData& right) const { return left.z < right.z; } }; struct TextureSorter { inline bool operator()(const SortData& left, const SortData& right) const { return left.pTexture < right.pTexture; } }; // the table, sort-function etc... template<typename Sorter> void sortFunction(SortingVector& vSortData) { std::stable_sort(vSortData.begin(), vSortData.end(), Sorter()); } using SortFunction = void (*)(SortingVector&); constexpr SortFunction sortFunctions[] = { &sortFunction<BackToFrontSorter>, &sortFunction<FrontToBackSorter>, &sortFunction<TextureSorter> ... }; Now as I said I would like to replace those functors with lambdas, but since I can't store the lambda itself but another somehow generate the code for "sortFunction" with the lambda instead of Sorter, I really don't know how to pull this off (storing the sort-function & calling std::stable_sort with the adress is not an option since it absolutely has to be inlined). Thats the closest I got so far: template<typename Sorter> void sortFunction(SortingVector& vSortData) { std::stable_sort(vSortData.begin(), vSortData.end(), Sorter()); // error: lambda default-ctor deleted } constexpr auto generateSortFunctions(void) { constexpr auto frontToBack = [](const SortData& left, const SortData& right) { return left.z > right.z; }; constexpr auto backToFront = [](const SortData& left, const SortData& right) { return left.z < right.z; }; constexpr auto texture = [](const SortData& left, const SortData& right) { return left.pTexture < right.pTexture; }; using SortFunction = void(*)(SortingVector& vSortData); constexpr std::array<SortFunction, 3> vFunctions = { &sortFunction<decltype(backToFront)>, &sortFunction<decltype(frontToBack)>, &sortFunction<decltype(texture)>, }; return vFunctions; } Unfortunately this doesn't work since you cannot instantiate a lambda directly. So, any ideas how this could work? Is this even possible (without lots of redundant work like writing a second lambda that includes the sort for every instantiation? std::function (which I suppose could work) & type-erasure should also be avoided if possible, since this is performance-critic code. Thanks!
  3. "T" in this case is the constructor of template-type "T" which is declared for foo. "get<Is>(args)..." gets every element from 0...last. Similar to "std::forward<Args>(args)" for variadic args, this expands to: T(get<0>(args), get<1>(args), get<2>(args)....); Think the general term for this is folding. "Is" is just a sequence of non-type template arguments that go from 0... last-index, based on the index_sequence variable you pass to the function.
  4. Yes, as far as I'm aware this is exactly what mutable is for. The dirty-flag/caching are just implementation-details that the client doesn't care about, so getResult() can be used identically as if doSomethingThatChangesResult just wrote the result directly. Its bettern than to mark would-be const-functions non-const, as this could easily break const-correctness in some major ways.
  5. C++ General C++ class questions

    You could alleviate the problem by using move-semantics: ResourceItem *ResourceCreator::createResourceItem(UINT size) { ResourceItem resourceItem; system->CreateResourceItem(size, &resourceItem.dataPointer); resourceItems.push_back(std::move(resourceItem)); // instead of copying resourceItem, move its content into the container, leaving "resourceItem" in an uninitialized state return &resourceItems.back(); } //resourceItem goes out of scope here. A call to the Release method in the destructor The move-ctor would look something like this: ResourceItem::ResourceItem(ResourceItem&& item) : dataPointer(item.dataPointer) { item.dataPointer = nullptr; } Then when the dtor is called for resourceItem, it behaves exactly as if you deleted a default ResourceItem-instance, so you could pack Release in the dtor (if this is what you wanted). On another note, even without move-semantics, why does this cause a problem in the first place? If you do reference-counting, copying the ResourceItem via copy-ctor should already by incrementing the ref-count by 1 (so on the push_back-line, you have 2 references, when ResourceItem goes out of scope it goes back to 1). In that case, move-semantics would be a tad faster OFC, but it should work eigther way if done right.
  6. Its certainly possible, as good as a metric that is. People have written successful 3d-games all in one file with horrible spaghetti-code: So you could certainly write such a type of RPG without decoupled rendering etc. . Some of the problems that usually come by suboptimal designs wouldn't matter for a 1990-area-style 2D game. On the other hand, since such an RPG does have a large scale by means of content, certain suboptimal design choices can bite you in the ass later on. I had to abort one of my first c++-games (a super-mario world clone) because at a certain point, it became impossible to add new features etc... without hours of mindnumbing, uncessary work. So yeah, what you ask is certainly possible - but I'd say the same if you asked "Can I get to work without walking on my feet"? Sure, if you don't care to use your legs and rather crawl, but why not just try to do it the right way? Unless you're very experienced, you won't have a perfect design anyway, but if you at least try to take the advice given you'll learn & see for yourself why its better to do it that way.
  7. If you want to specialize based on a another range of types, sure, for specialization based on single types you wouldn't need it though: template<typename Type> struct Vector2 { }; template<> struct Vector2<double> { }; So unless you actually know what different set of types you'd want to specialize against, I'd put it under YAGNI, and stick with the solution that fits more right now. Cannot really answer that. Expression-SFINEA certainly required a compiler-change from what I can tell, but as for the older parts of SFINEA, I cannot tell.
  8. What you are doing is called SFINEA. Theres not that much magic going on: The first declaration of Vector2 is the actual declaration of Vector2. "typename Enable = void" is just a regular template parameter with a default value of "void" (which is there so you can use Vector2 with only one argument). The second declaration is actually a spezialisation of the first (lookup template spezialisation when you are unsure about that), and due to SFINEA rules and how std::enable_if works, it will only be available when std::is_pod is actually satisfied - for all other types, it will select the unspecialized declaration #1. Thats all there is to it Actually, I'd personally just use one vector-declaration, and use a static-assert instead: template<typename Type> struct Vector2 { static_assert(std::is_pod_v<Type>, "Type must be pod."); }; Almost same effect, easier to understand code, and easier-to-understand error reporting, IMHO. Also, wouldn't you want to check for std::is_arithmetic instead, or do you specifically want to support custom-POD structs as well?
  9. Alternatively, you pass relative file-paths to functions, but use something like SetCurrentDirectory/chdir/... before to "fixup" the filepath. I've personally found that approach pretty viable at least for a lot of operations (ie. the asset-loader will change the current dir before scanning the content-folder for assets). You just have to make sure the change the directory back after the fact, which I decided to use a stack-based solution: const WorkingDirectory dir(L"../Game"); // change current directory from APPLICATION/Bin to APPLICATION/Game { const WorkingDirectory dir(L"Scenes"); // change to Game/Scenes sceneIO.LoadSceneDeclarations(); } // back to Game/ { const WorkingDirectory dir(L"Assets"); // change to Game/Assets assetIO.LoadAssets(); } // back to Game/ again Which is probably part of the reason why it works well for me in the first place. So this way, I seldomly have to pass a full filepath or a folder-path at all (even if I do I usually just create a WorkingDirectory-struct at the header of the function if it involves multiple file-operations, seems way cleaner to me then to string-construct all filepaths to the full-path).
  10. Appearently there are different meanings for copy ellision, you are right, the one I was refering to was the recent one, from the top of the cppreference-ink: Seems you're right about that one, but yeah, as you see the compiler is only permitted to ignore the copy/move, while in the case of the new c++17 rule I was refering too, it is required to do so, making the following code legal (that was previously not): struct NonCopyable { NonCopyable() { } NonCopyable(const NonCopyable&) = delete; void operator=(const NonCopyable&) = delete; NonCopyable(NonCopyable&&) = delete; void operator=(NonCopyable&&) = delete; } NonCoypable factory(void) { return NonCoypable(); // pre-c++17, this does not compile. } Its suprisingly hard to determine whats what here, but I think its important to realize that there are different things going on here conceptually.
  11. Contrary to what I originally hinted at and belived at the time, copy elision does not work on named temporaries. So without RVO, thats exactly what should be happening here. Note that there's also a slight, but important misconception here: copy ellision is NOT an optimization, its a guarantee towards the compiler to entirely omit anything related to copying/moving a value. Thats means, under copy/move ellision, there doesn't even need to be a valid copy/move-ctor (can be deleted or inacessessible); and any compiler that assumes otherwise is broken. Likewise, the difference between returning by value pre-c++ 11 and post-c++11 is important as well - with move-semantics, you can legitametly return a non-copyable class that has a valid move-ctor, while without move-semantics, you cannot do that; since RVO is an optional optimization that may or may not be present - thus a return-statement must be valid even without it present. With move-semantics, you can define classes as non-copyable and still return them by value; and you also have the guarantee to not have any copyies made i.e. in debug-builds.
  12. Did you test to see whats happening? Eighter with a debugger or std::cout/printf-statements? As has been pointed out, with full optimizations no copies or destructions should be happen here at all. In any case: This is not happening, due to copy elision the temporary returned by "test()" is considered non-existant and instead the returned content is placed direclty into the "shade" variable. So this line should read: VetexShader shade = shaderModule.test(); // no copy-ctor or destructor called at all here
  13. Are these all single components? phyiscs_owner, phyiscs_ownee, HP, ...? If so, it does sound needlessly complex, as I laid out in the other post there's really not much point of having stuff like "filename" as a component, but you should see for yourself On the same note, having 5-7 entities for a single rocket sounds like huge overkill. Personally, I would have a rocket-mesh with a Mesh & Physics-component, and thats pretty much it; unless the rocket specifically needs sub-entities like a particle-emitter (Thats just what I came to agree on based on 4 years of working with an ECS across different projects; you might come to a different conclusion; though based on your workflow/toolchain, a certain few things like having many components & entities can make creating new content a nightmare). I honestly don't agree to that statement. Whats wrong with using a Transform-component to communicate information between different systems, that all might have their own internal transform-structure? You're using data to transfer information anyways at some point, like sending information across a network, or storing and later loading a save-file. There's not code inside the save-file, its just data at that point, but because you agreed on a format, its save to store at one point and load at a later point. Thats how I see it with this example of the transform as well: You agree on a shared "format" for world-transform on a shared "Transform"-component, and systems can read/write to the transform while still being able to internally use their own data. You can still use buisness-logic to make the writing/reading of this transform-data more safe, but I don't see the benefit of code-based communication vs a data-"stream" of sorts.
  14. Eh, forget that part, I had a brainfart and assumed std::unique_ptr would reallocate elements as it grows. See my edit, thats how I would do it. Still I don't quite get your question. What i indendet was: std::unordered_map<LPCSTR, std::unique_ptr<VertexShader>> vertexShaders; But since (I hope I've got it right now that) its safe to keep a reference to an element in an unordered_map, thats unnecessary.
  15. Thats not the case in modern C++ (>11) anymore. With a proper move-constructor/assignment operator, instead of creating a copy it will move the data of the local "vertexShader" variable into the target on the callers side. If you define VertexShader as non-copyable (deleted copy- ctor/assignment-operator) then you can make sure this is happening all the time, even before the recent http://en.cppreference.com/w/cpp/language/copy_elision. (which is different to RVO; but probably won't happen if you assign to a global like in the example-code) However, one place this code certainly does create a copy is this: So the code as shown still is flawed in this way. But, you could easily do: VertexShader& ShaderModule::createVertexShader(LPCWSTR fileName, LPCSTR id, LPCSTR entryPoint, LPCSTR targetProfile) // returns reference { ID3DBlob *shaderCode = compileShaderFromFile(fileName, entryPoint, targetProfile); D3D11_INPUT_ELEMENT_DESC inputElementDescription[] = { { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 }, { "COLOR", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 }, }; VertexShader& vertexShader = vertexShaders.emplace(fileName, id).first->second; // construct the shader inside the map => might need to adjust the call at the end, not sure about the iterator-syntax device->CreateVertexShader(shaderCode->GetBufferPointer(), shaderCode->GetBufferSize(), NULL, &vertexShader.shader); device->CreateInputLayout(inputElementDescription, 2, shaderCode->GetBufferPointer(), shaderCode->GetBufferSize(), &vertexShader.inputLayout); shaderCode->Release(); return vertexShader; }