Wolfenclone

The Partridge Family were neither partridges nor a family. Discuss.
Firepath
Posts: 77
Joined: March 10th, 2018, 11:53 pm

Re: Wolfenclone

Post by Firepath » August 27th, 2018, 11:46 am

Nah I'm using it like Chili said / like I said in my last post - I'm using std::unique_ptr<T> to store things and passing a pointer from it (unique_ptr.get()) where I need to hold a reference to something but not own / manage its lifetime.

As an example of how I'm using it:

I have a templated repository class StringKeyRepository<T> (for lack of a better name that is shorter). It stores a std::unique_ptr<T> using a std::string as the key.

I have repositories of Surfaces (textures), Fonts, MapFixtures, EditTools that all own the single instance of each that is required.

From there, with the insert menu, for instance, each menu item has a callback object that holds a raw pointer to the one insert tool instance, and a raw pointer to whichever fixture it is for. When selected it sets the fixture into the tool (so starts off null in the tool / needs to be null at some point) and sets the tool into the editor as the left mouse click tool.

So the menu items have raw pointers to tools and fixtures, the editor has raw pointers to tools (left and right button tools), etc. Each grid cell filled with a fixture only has a raw pointer to the fixture, which no doubt will be used many times per map.

All the ownership of these objects is in the repositories as std::unique_ptrs and all the places that need to use it at will just have a raw pointer. I'm also trying to make temporary uses (in scope of a single function / method) use get methods that return by reference as I don't need null and want the simpler (slightly, you know what I mean) use vs pointer.

Its good to get some understanding (if not confirmation that it is an OK idea) of what I'm doing. It means I'm not doing things completely bonkers. I also am happy to read your description of the point of shared pointers, it is the impression I have had of them. It also make all that I have read saying it should be rarely used make sense as I also believe generally you should know where the ownership exists. Sure there are cases where it is legitimate but I'd say for most, simple / basic / general logic it is probably reasonable to have specific ownership control of your objects.

User avatar
chili
Site Admin
Posts: 3948
Joined: December 31st, 2011, 4:53 pm
Location: Japan
Contact:

Re: Wolfenclone

Post by chili » August 27th, 2018, 2:28 pm

@papa

It's a perfectly fine use case and it is definitely best practice to not use shared_ptr when there is no concrete motivation to do so. In fact, the standard committee is making a new kind of "smart pointer" to make this clear (pretty much expressly for ppl who can't wrap their head around how smart_ptrs and dumb pointers can be used together). It's called observer_ptr



std::experimental::observer_ptr is a non-owning pointer, or observer. The observer stores a pointer to a second object, known as the watched object. An observer_ptr may also have no watched object.

An observer is not responsible in any way for the watched object; there is no inherent relationship between an observer and the object it watches.

It is intended as a near drop-in replacement for raw pointer types, with the advantage that, as a vocabulary type, it indicates its intended use without need for detailed analysis by code readers.



Basically, it's a raw ptr, but as a vocabulary type it is self-documenting (no ambiguity as to whether delete needs to be called, etc.)
Chili

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: Wolfenclone

Post by albinopapa » August 27th, 2018, 8:45 pm

@Firepath, yeah that's how I figured you were using them so we are on the same page.

Honestly, I don't know how I would have handled the situation. My first thought would be just to store the actual objects in your resource structure instead of unique_ptr to the objects and when needed, use the address of those objects.

Code: Select all

struct Texture
{
     std::unique_ptr<Color[]> pixels;
     size_t width = 0,height = 0;
}

struct TextureResource
{
     Texture* FindTexture( std::string TexName )
     {
          if( TexName == "wall" ) return &wall;
          else if( TexName == "painting" ) return &painting;
          else if( TexName == "hitler" ) return &hitler;
          else return nullptr;
     }
     Texture wall,painting,hitler;
};

class Grid
{
     class Cell
     {
     public:
          SetTexture( Texture* pTex )
          {
               pTexture = pTex;
          }
          Texture* pTexture;
     };
public:
     void SetCellTexture( int CellIndex, std::string TextureName )
     {
          cells[ CellIndex ].SetTexture( FindTexture( std::move( TextureName ) ) )
     }
private:
     std::vector<Cell> cells;
};
Something like this I suppose is what I would do. I try not to use heap allocated objects whenever possible. So in the end, I still store raw pointers to objects that have the potential of "dangling" same as unique_ptr, so it's kind of a useless thing to argue lol. Anyway, good chat.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

Firepath
Posts: 77
Joined: March 10th, 2018, 11:53 pm

Re: Wolfenclone

Post by Firepath » August 27th, 2018, 9:24 pm

albinopapa wrote:@Firepath, yeah that's how I figured you were using them so we are on the same page.

Honestly, I don't know how I would have handled the situation. My first thought would be just to store the actual objects in your resource structure instead of unique_ptr to the objects and when needed, use the address of those objects.

Code: Select all

struct Texture
{
     std::unique_ptr<Color[]> pixels;
     size_t width = 0,height = 0;
}

struct TextureResource
{
     Texture* FindTexture( std::string TexName )
     {
          if( TexName == "wall" ) return &wall;
          else if( TexName == "painting" ) return &painting;
          else if( TexName == "hitler" ) return &hitler;
          else return nullptr;
     }
     Texture wall,painting,hitler;
};

class Grid
{
     class Cell
     {
     public:
          SetTexture( Texture* pTex )
          {
               pTexture = pTex;
          }
          Texture* pTexture;
     };
public:
     void SetCellTexture( int CellIndex, std::string TextureName )
     {
          cells[ CellIndex ].SetTexture( FindTexture( std::move( TextureName ) ) )
     }
private:
     std::vector<Cell> cells;
};
Something like this I suppose is what I would do. I try not to use heap allocated objects whenever possible. So in the end, I still store raw pointers to objects that have the potential of "dangling" same as unique_ptr, so it's kind of a useless thing to argue lol. Anyway, good chat.
I was going to bring that up in my last post but didn't want to muddy my already long post. I probably don't need the pointers stored in my repos. It brings up a question for me about when to not point to things. Sure I can have a pointer to an object and leave its members not pointers but what if I use the same class without a pointer? I'm mainly concerned with filling the stack, which I have had happen occasionally before. Maybe it was due to bad design but I can see good designs having the same issue at some point. I need to read more about it. I want to have members mainly be stored without pointers as it feels correct and simpler but feel like I might temporarily use the class for a loop in a function and poof - overflow. Should those be dealt with on a case by case basis or mitigated through their design?

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: Wolfenclone

Post by albinopapa » August 27th, 2018, 9:40 pm

You are by default in VS allocated 1MB of stack space. If you are going to have a couple of hundred objects, you might get away with using a C array or std::array, but a few thousand or more objects, you might just stick with a vector which uses heap allocation.

If you look at the code I posted, the Texture is implemented using the array overload of std::unique_ptr for the pixel data which would of course be heap allocated, while the Texture object and the member std::unique_ptr would both be stack allocated. So the Texture class might only take up 4 to 8 bytes of stack space and the underlying pixel data could be a couple megabytes.

The std::vector<Cell> cells object would only take up space for the pointer that holds the array of Cell objects and depending on implementation the size of the array or a pointer/iterator to the end of the array for which size can be calculated. This means for a grid of cells, you at most use up 4 to 8 bytes for the array pointer and another 4 to 8 bytes for either the size_t or end of array pointer and maybe a little more space for the allocator reference.

Now if you have an object that actually had a few megabytes of data members ( a C array of 1024x1024] or something along those lines, then you'd be forced to allocated that object instances of that type on the heap. Just think about that, ~256,000 ints can be stored or ~128,000 64bit pointers can be stored on the stack before getting a stack overflow. Another quick way of causing a stack overflow would be recursion and having a lot of depth or a lot of local variables in the recursive function.

I guess what I'm getting at is, for something like WolfenClone, you probably aren't very likely to run out of stack space. As an alternative, VS allows you to change the amount of stack space allocated to you, if you find you really need more.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

Firepath
Posts: 77
Joined: March 10th, 2018, 11:53 pm

Re: Wolfenclone

Post by Firepath » August 28th, 2018, 3:25 am

Recursion is definitely the problem most of the time for me. I like to make that CPU work! The logic for working out closed areas was recursive but I made it more effective by maintaining lists of cells to check and iterating instead. Plus no more pesky overflows. But mainly I did it due to only maintaining a list of non-empty cells so recursing through empty ones was no longer an option.

User avatar
chili
Site Admin
Posts: 3948
Joined: December 31st, 2011, 4:53 pm
Location: Japan
Contact:

Re: Wolfenclone

Post by chili » August 28th, 2018, 5:40 am

Often times I find myself using heap storage not because I want to conserve stack space, but because I want to leverage polymorphism. That being said, where there is no actual motivation (like polymorph or stack size), automatic storage is the way to go. Also, if you are using heap storage for embeded objects to save space, but the enclosing stack is already on the heap (like maybe in a vector), you gain nothing in that regard.
Chili

Firepath
Posts: 77
Joined: March 10th, 2018, 11:53 pm

Re: Wolfenclone

Post by Firepath » August 28th, 2018, 10:49 am

chili wrote:Often times I find myself using heap storage not because I want to conserve stack space, but because I want to leverage polymorphism. That being said, where there is no actual motivation (like polymorph or stack size), automatic storage is the way to go. Also, if you are using heap storage for embeded objects to save space, but the enclosing stack is already on the heap (like maybe in a vector), you gain nothing in that regard.
Yep I thought about it after I posted about not needing pointers in my repos but yeh I am storing polymorphic objects of the same base class together. So you can only do that as pointers, right?

And yeh about the heap inside heap stuff that is what I was wondering before. Where do you start and stop with using pointer? Keep everything stack based until a member of a member... is heavyweight enough to warrant pointing to it on the heap? Point at your top level object on the heap and leave all its members and their members... as not pointers? Some combination of both? Does it depend on your application or style preference? I can see plus and minus going either way, mainly if you use something that you built to be on the heap directly on the stack will it cause problems? I also feel that a class should control its own weight putting things on the heap where needed. Obviously polymorphism needs pointers. I feel the mixed approach is probably right, applying reason to your choices.

Firepath
Posts: 77
Joined: March 10th, 2018, 11:53 pm

Re: Wolfenclone

Post by Firepath » August 28th, 2018, 10:58 am

Also I probably should have read your (Chili) reply a few more times before replying. I have to spend a bit more time learning about the things I'm using (std::unordered_map and vectors so far). I assume vector and map will be storing on the heap (if they think they need to?) but that is assumption.

User avatar
chili
Site Admin
Posts: 3948
Joined: December 31st, 2011, 4:53 pm
Location: Japan
Contact:

Re: Wolfenclone

Post by chili » August 28th, 2018, 5:41 pm

There is no if they think about it, all containers except std::array store on the heap. std::string has a special case for very short strings where it can store on the stack for them. (sso)
Chili

Post Reply