"Dym" po wystrzale z broni, pochodnie, piksele wydobywające się z różnych obrazków

0

Zrobiłem tablicę niedynamiczną i teraz to w ogóle mi całą apke kraszuje.

Zrobiłeś tablicę niedynamiczną, ale wskaźników. Cząsteczki nadal alokujesz dynamicznie, więc zysk praktycznie żaden.

for (int i = 0; i < MAX_PARTICLES; i++)
    {
        //if (pe[i]->nextParticle())
        //{
            //delete pe[i];
            pe[i] = new ParticleEngine(posX, posY, !moveDirection, renderer);
        //}
    }

Nie wiem, gdzie Ci crashuje, fajnie, jakbyś pokazał screen z debuggera. Ale strzelam, że próbujesz usunąć już usunięty obiekt, albo go użyć.
To, co Ci proponowałem, to z grubsza coś takiego:

const int MAX_PARTICLES = 10000;
ParticleEngine* ParticlePool = new ParticleEngine[MAX_PARTICLES];

//alokacja nowych cząstek
ParticleEngine* particle = new(ParticlePool[i]) ParticleEngine(posX, posY, !moveDirection, renderer); //placement new - tworzysz particle'a w już zaalokowanej pamięci
//powyższe ma tę wadę, że musisz pilnować indeksu i alokowac/dealokować w odpowiedniej kolejności 

Aby zrobić memory pool, który będzie sobie sam zarządzał wolną pamięcią i nie będzie miała znaczenia kolejność, musisz opakować sobie potrzebną pamięć w jakiś node i trzymać komórki memory poola w postaci listy (tzw. free list) - internecie znajdziesz sporo materiałów na ten temat. To trochę większy narzut pamięci, ale alokacja/dealokacja jest bardziej elastyczna. Niemniej mam wrażenie po dotychczasowych postach, że przydałoby Ci się doczytać podstaw na temat alokacji pamięci w C++ w ogóle i o tym jak jest ona zorganizowana.

BTW. Inna rzecz, że wystarczyłoby, jakbyś w poprzedniej wersji zrobił taką modyfikację:

std::vector<ParticleEngine> Particles; //Zauważ, że vector nie trzyma wskaźnika, tylko cały obiekt
//....
//Gdzieś w konstruktorze albo przed pętlą
Particles.reserve(MAX_PARTICLES); //albo jakakolwiek inna wartość, jeśli jest zmienna
//teraz to kosztuje praktycznie tyle co wywołanie konstruktora na ParticleEngine
Particles.push_back(ParticleEngine(...)); //zauważ brak new
//waże też byś zdefiniował konstruktor przenoszący dla ParticleEngine - unikniesz wtedy niepotrzebnego kopiowania przy tworzeniu particle'a

Ogólnie pamiętaj, że, wywoływując samo new, nie masz żadnej kontroli na to, w któym miejscu w pamięci obiekt zostanie stworzony i to że wskaźniki do tych obiektów masz koło siebie, niczego tak naprawdę nie zmienia. To obiekty mają być koło siebie.

0

Crashować nie crashuje, ale są straszne lagi + przy niedynamicznej tablicy renderują mi się wszystkie cząsteczki na raz, jest to dobre tylko do niektórych rzeczy. Tymczasem zostanę sobie przy dynamicznej. Chciałem stworzyć base klasę Particle dla każdego rodzaju cząsteczek i ParticleManager, który będzie miał metodę

ParticleManager::AddParticle(Particle *pParticle)
{
     particle.push_back(pParticle);
}

//i na usuwanie
void ParticleManager::DeleteParticle()
{
	for (const auto &p : particle)
	{
		delete p;
	}
	pe.clear();
}

Co o tym myślisz?

Można by wtedy dodać jeszcze jedną niedynamiczną tablicę dla jakichś wyjątków. Ale czy to nie wystarczy?

  • Dobrym pomysłem jest poszukanie wszystkich pointerów, które są nie potrzebne (nie mają wpływu na wydajność, bo zdecydowanie moby i cząsteczki coś się pierdzielą) i zamienić je na normalne obiekty?
0

Nadal myślę, że tworzenie każdego particle'a na stercie osobno jest niezgodne ze sztuką. Tak się po prostu nie robi. I nie chodzi tu o to, żeby je tworzyć statycznie/dynamicznie, tylko o to, żeby były one (stworzone już obiekty a nie wskaźniki do nich) w jednym ciągłym bloku pamięci. To, że wyswietlały sie wszystkie na raz sugeruje, że gdziesz popełniłeś błąd. Ale zamiast zrozumieć jak to działa i jak powinno działać, stosujesz Shotgun Debugging.
Podałem Ci kilka praktyk, które stosuje się powszechnie, są dobrze opisane i w miarę wydajne. Po tych wszystkich postach mam wrażenie, że niepotrzebnie się produkuję, bo i tak wygląda to tak, jakbyś w ogóle nie czytał co piszę do Ciebie. Jeśli czegoś nie rozumiesz to napisz konkretnie z czym masz problem, a postaram się wyjaśnić.

Proponuję też skorzystać z profilera i zobaczyć, co faktycznie najwięcej zajmuje (daj screeny). Wrzuć więcej kodu, a nie tylko wyrwane z kontekstu fragmenty. Ciężko na tej podstawie mieć lepszy pogląd na całość.
Fajnie, jakbyś wrzucił całą klasę Particle, Gun i inne powiązane. BTW. W pierwszej wersji w konstruktorze ParticleEngine za każdym razem tworzyłeś nowego sprite'a, mimo, że były ich tylko 4 rodzaje. Pisałem Ci, żebyś użył wzorca Flyweight, @czaffik to powtórzył w komentarzu, ale mam wrażenie, że nadal tego nie zrobiłeś. Chciałbym zobaczyć jak się odniosłeś do poprzednich rad, a jeśli je odrzuciłeś to dlaczego.

0

Tekstur już nie ładuje (nie bynajmniej dla cząsteczek). Zamieniłem na SDL_Rect. Tak wyglądają klasy

#pragma once
#ifndef PARTICLE_H
#define PARTICLE_H

#include <SDL.h>
#include <cstdlib>

class Particle
{
public:

	typedef enum ParticleState
	{
		pDead = -1,
		pLive = 0
	};
	ParticleState particleState;

	Particle();
	~Particle();

	virtual void Update();
	virtual void Draw(SDL_Renderer *renderer);
        virtual void UpdatePosX();

protected:
	SDL_Rect particleRect;

	float alpha;

	float posX, posY;
	float velX, velY;

	int moveDirection;
};

#endif // ! PARTICLE_H
Particle::Particle()
{
	this->particleState = pLive;
}

Particle::~Particle() {}

void Particle::Update(){}
void Particle::Draw(SDL_Renderer * renderer){}
void Particle::UpdatePosX()
{
	if (moveDirection)
	{
		posX -= velX;
	}
	else
	{
		posX += velX;
	}
}

Zmieniłem klasę z ParticleEngine na BulletParticle

#pragma once
#ifndef  PARTICLEENGINE_H
#define PARTICLEENGINE_H

#include "Particle.h"

class BulletParticle : public Particle
{
public:
	BulletParticle(float x, float y, int moveDirection);
	~BulletParticle();

	void Draw(SDL_Renderer *renderer);
	void Update();
private:	
};
#endif // ! PARTICLEENGINE_H
#include "BulletParticle.h"

BulletParticle::BulletParticle(float x, float y, int moveDirection)
{
	this->posX = moveDirection ? x + 16 : x - 4;
	this->posY = y + 6;
	this->moveDirection = moveDirection;
	this->velY = (rand()%6-3) * 0.5;
	this->velX = rand() % 6;
	this->alpha = 100;
	this->particleState = pLive;
}

BulletParticle::~BulletParticle() {}

void BulletParticle::Draw(SDL_Renderer * renderer)
{
	this->particleRect = { (int)posX, (int), 5, 5 };
	if (particleState == pLive)
	{
		SDL_SetRenderDrawColor(renderer, 255, 255, 255, alpha);
		SDL_RenderFillRect(renderer, &particleRect);
	}
}

void BulletParticle::Update()
{
	alpha -= 3;
	if (alpha <= 0)
	{
		particleState = pDead;
	}

	UpdatePosX();
	posY += velY;
}

I broń

#pragma once
#ifndef PLAYERGUN_H
#define PLAYERGUN_H

#include "Entity.h"

class PlayerGun : public Entity
{
public:
	PlayerGun(int posX, int posY, int moveDirection, SDL_Renderer *renderer);
	~PlayerGun();

	void Draw(SDL_Renderer *renderer);
	void Update();
	void UpdatePosX();

	void DrawPE(SDL_Renderer *renderer);
private:
	std::vector<BulletParticle*> bulletPart;
	int sizePE;
	int maxMoveSpeed;
};
#endif
#include "PlayerGun.h"

PlayerGun::PlayerGun(int posX, int posY, int moveDirection, SDL_Renderer *renderer)
{
	this->posX = posX;
	this->posY = posY;
	this->moveDirection = moveDirection;

	this->bDestroy = false;

	this->maxMoveSpeed = 15;
	this->moveSpeed = 0;
	this->jumpSpeed = 2;

	this->hitBoxX = 16;
	this->hitBoxY = 16;

	this->eSprite = Texture("playerBullet", renderer);
}

PlayerGun::~PlayerGun()
{
        for (auto &p : bulletPart)
	{
		delete p;
	}
        bulletPart.clear();
	Entity::~Entity();
}

void PlayerGun::Draw(SDL_Renderer * renderer)
{
	for (int i = 0; i < 5; i++)
	{
		bulletPart.push_back(new BulletParticle(posX, posY, moveDirection));
	}

	DrawPE(renderer);
	eSprite.Draw(posX, posY, renderer, !moveDirection);

	for (auto &p : bulletPart)
	{
		bulletPart.erase
			(
				std::remove_if(bulletPart.begin(), bulletPart.end(), [](const Particle* p)
					{
						return p->particleState == Particle::pDead;
					}
				),
				bulletPart.end()
			);
	}
}

void PlayerGun::Update()
{
	if (bDestroy)
	{
		entityState = eDead;
	}
	else
	{
		for (const auto &p : bulletPart)
		{
			p->Update();
		}
		UpdatePosX();	
	}
}

void PlayerGun::DrawParticle(SDL_Renderer * renderer)
{
	for (const auto &p : bulletPart)
	{
		p->Draw(renderer);
	}
}

void PlayerGun::UpdatePosX()
{
	if (moveSpeed >= maxMoveSpeed)
	{
		--moveSpeed;
	}
	else
	{
		++moveSpeed;
	}

	if(moveDirection)
	{
		posX -= moveSpeed;
	}
	else
	{
		posX += moveSpeed;
	}

	if (posX < -300 || posX  > 1280 + 300)
	{
		bDestroy = true;
	}
}

Nie wiem czy chcesz zobaczyć Entity klasę w całości, bo trochę jest tego dużo. Jedynie co z niej biorę to:

posX, 
posY, 
velX,
 velY, 
moveSpeed,
 moveDirection, 
hitBoxX, 
hitBoxY, 
eSprite, 
bDestroy,
i destruktor, który czyści eSprite

EntityManager

#pragma once
#ifndef ENTITYMANAGER_H 
#define ENTITYMANAGER_H

#include "Human.h"
#include "Cleaver.h"
#include "PlayerGun.h"
#include "FileManager.h"

class EntityManager
{
public:
	typedef enum EntityType
	{
		none = 0,
		player = 1,
		human = 2,
		playerGun = 3,
		cleaver = 4,
	};
	EntityType currentType;

	EntityManager();
	~EntityManager();

	void Update();
	void Draw(SDL_Renderer *renderer);

	void AddComponent(std::string name, EntityType type, SDL_Renderer *renderer);
	void AddPlayerGun(int x, int y, int moveDirection, SDL_Renderer *renderer);

	void CreateEntity(std::vector<std::vector<std::string>> map);
	void ClearEntity();

	int getList(int x);

	int getNumOfEntities();

private:
	FileManager file;
	std::vector<std::vector<Entity*>> ent;

	int entitySize;
};
#endif // ! 
#include "EntityManager.h"

EntityManager::EntityManager()
{
	this->currentType = none;
}

EntityManager::~EntityManager()
{
	for (auto &e : ent)
	{
		for (const auto &en : e)
		{
			delete en;
		}
		e.clear();
	}
	ent.clear();
}

void EntityManager::Update()
{
	for (const auto &e : ent)
	{
		for (const auto &en : e)
		{
			if (en->entityState == Entity::eLive)
			{
				en->Update();
			}
		}
	}
	
	for (auto &e : ent)
	{
		e.erase
		(
			std::remove_if(e.begin(), e.end(), [](const Entity* entity)
				{
					return entity->entityState == Entity::eDead;
				}
			),
			e.end()
		);
	}
}

void EntityManager::Draw(SDL_Renderer * renderer)
{ 
	for (const auto &e : ent)
	{
		for (const auto &en : e)
		{
			if (en->entityState == Entity::eLive)
			{
				en->Draw(renderer);
			}
		}
	}
}

int EntityManager::getList(int x)
{
	return x / 50;
}

int EntityManager::getNumOfEntities()
{
	int num = 0;
	for (const auto &e : ent)
	{
		num += e.size();
	}
	return num;
}

void EntityManager::AddComponent(std::string name, EntityType type, SDL_Renderer *renderer)
{
	std::vector<std::vector<std::string>> attributes;
	std::vector<std::vector<std::string>> contents;

	std::vector<float> eX, eY, eMS, eRG, eMD, cRadius;

	file.LoadFromFile(name, attributes, contents, name);

	for (int i = 0; i < attributes.size(); i++)
	{
		for (int j = 0; j < attributes[i].size(); j++)
		{
			std::string att = attributes[i][j];
			std::string con = contents[i][j];

			float conf = atof(con.c_str());
			int coni = atoi(con.c_str());

			if (att == "x") eX.push_back(conf);
			else if (att == "y") eY.push_back(conf);
			else if (att == "ms") eMS.push_back(conf);
			else if (att == "range") eRG.push_back(conf);
			else if (att == "moveDirection") eMD.push_back(coni);
			else if (att == "radius") cRadius.push_back(coni);
		}	
	}	

	for (int i = 0; i < contents.size(); i++)
	{
		switch (type)
		{
			case human:
				ent[getList(eX[i])].push_back(new Human(eX[i], eY[i], eMS[i], eRG[i], eMD[i], renderer));
				break;
			case cleaver:
				ent[getList(eX[i])].push_back(new Cleaver(eX[i], eY[i], cRadius[i], 0, eMD[i], renderer));
				break;
		}
	}
}

void EntityManager::AddPlayerGun(int x, int y, int moveDirection, SDL_Renderer *renderer)
{
	ent[getList(x)].push_back(new PlayerGun(x,y, moveDirection, renderer));
}

void EntityManager::CreateEntity(std::vector<std::vector<std::string>> map)
{
	for (int y = 0; y < map.size(); y+=5)
	{
		std::vector<Entity*> tempEnt;
		for (int i = 0; i < map[y].size(); i++)
		{
			ent.push_back(tempEnt);
		}
	}

	entitySize = ent.size();
}

void EntityManager::ClearEntity()
{
	for (auto &e : ent)
	{
		for (const auto &en : e)
		{
			delete en;
		}
		e.clear();
	}
}
1

Właśnie coś sobie jeszcze uświadomiłem. Wydaje mi się, że źle interpretujesz zużycie procesora w kwestii wydajności. Zużycie procka na poziomie 30% świadczy o tym, że w 70% nic on nie robi (prawdopodobnie czeka on aż GPU skończy rysować), a nie, że masz niewydajne rozwiązanie. Mierz czas jednej klatki i czas samego renderowania - to ci da pogląd na wydajność. Tutaj potrzebny jest balans między obciążeniem CPU i GPU. To jest bardzo szeroki temat i wiele czynników może wpływać na jedno i drugie, dlatego najlepiej odpalić program w profilerze i sprawdzić co ile zajmuje.

0

Masz w klasach PlayerGun i EntityManager wycieki pamięci:

void PlayerGun::Draw(SDL_Renderer * renderer)
{
    for (int i = 0; i < 5; i++)
    {
        bulletPart.push_back(new BulletParticle(posX, posY, moveDirection));
    }

    DrawPE(renderer);
    eSprite.Draw(posX, posY, renderer, !moveDirection);

    for (auto &p : bulletPart)
    {
        bulletPart.erase //O TUTAJ!
            (
                std::remove_if(bulletPart.begin(), bulletPart.end(), [](const Particle* p)
                    {
                        return p->particleState == Particle::pDead;
                    }
                ),
                bulletPart.end()
            );
    }
}

Lepiej zrób tak:

void PlayerGun::Draw(SDL_Renderer * renderer)
{
    for (int i = 0; i < 5; i++)
    {
        bulletPart.push_back(new BulletParticle(posX, posY, moveDirection));
    }

    DrawPE(renderer);
    eSprite.Draw(posX, posY, renderer, !moveDirection);

    for (auto &p : bulletPart)
    {
        auto RemoveIt = std::remove_if(bulletPart.begin(), bulletPart.end(), [](const Particle* p)
                    {
                        return p->particleState == Particle::pDead;
                    });
        for (auto it = RemoveIt; it != bulletPart.end(); ++it)
        {
            delete *it; //usuwamy obiekt, na który wskazuje wskaźnik
        }

        bulletPart.erase( RemoveIt, bulletPart.end() ); //teraz można spokojnie wywalić wskaźniki z wektora
    }
}

Ponadto powtórzę po raz trzeci (chyba):
Niech PlayerGun zamiast std::vector<BulletParticle*> bulletPart; ma std::vector<BulletParticle> bulletPart; - to najprostsza zmiana, która spowoduje, że znikną wycieki pamięci (wówczas powyższe zmiany w kodzie są niepotrzebne), będziesz miał wszystkie particle w jednym bloku pamięci. Jak jeszcze do tego przed pętlą z push_back wywołasz reserve, to będzie jeszcze lepiej.

0

Skąd ten wyciek? Znaczy, rozumiem, że metoda źle napisana, ale wprowadziło to co dopisałeś?

Teraz to mi w ogóle coś takiego sie pokazalo
screenshot-20171106143416.png

I nadal gdzieś ta luka w pamięci musi być. No chyba, że to tak powinno działać, że przy zrenderowanych 3 mobkach pamięć zwiększa się 1 MB co ~50 sekund

0

Owszem sugerowałem użycie remove_if, ale też pisałem abyś pousuwał wszystko:
Post z 31.10.2017 (9:45):

A nie doczytałem linijki, że chcesz usunąć ent[i][j]. Ja bym to usuwał w update. Możesz najpierw wywołać remove_if i zapisać zwracany iterator do zmiennej.
Później przeiteruj od tego iteratora do końca i wywołuj delete na każdym elemencie.
Następnie zrób erase(iterator, end())

Ten segmentation fault masz prawdopodobnie, gdyż próbujesz użyć obiektu, który został usunięty z pamięci. To znaczy, że wywołałeś na wskaźniku delete (usunąłeś particle), ale nie usunąłeś odwołania do niego, tj. wskaźnika. W wektorze trzymasz wskaźniki a nie obiekty. Wywołując delete, usuwasz to na co ten wskaźnik wskazuje i nie możesz tego używać. Musisz zatem pozbyć się tego wskaźnika metodą erase.
Zapomniałem zapytać. Ty to wszystko na jednym wątku robisz? Czy update jest na jednym a draw na drugim? Ponadto to usuwanie powinno być w update - tak jest logiczniej.

0

Mutexów nie używam, jeszcze.

Ahaa, bo ja tu usuwanie zrobiłem w rysowaniu, trochę niedopatrzenie.

Oki, stworzyłem obiekt zamiast wskaźnika. Post wyżej napisałeś, że wtedy twoje zmiany będą niepotrzebne (tak przynajmniej zrozumiałem), ale kiedy zostawiam

for (int i = 0; i < bulletPart.size(); i++)
	{
		bulletPart.erase
		(
				std::remove_if(bulletPart.begin(), bulletPart.end(), [](const Particle* p)
					{		
						return p->particleState == Particle::pDead;
					}
				),
				bulletPart.end()
		);
	}

jest błąd w kompilacji

		// TEMPLATE FUNCTION remove_if
template<class _FwdIt,
	class _Pr> inline
	_FwdIt _Remove_if_unchecked(_FwdIt _First, _FwdIt _Last, _Pr& _Pred)
	{	// remove each satisfying _Pred
	_First = _Find_if_unchecked(_First, _Last, _Pred);
	_FwdIt _Next = _First;
	if (_First != _Last)
		{
		while (++_First != _Last)
			{
			if (!_Pred(*_First))
				{
				*_Next = _STD move(*_First);
				++_Next;
				}
			}
		}

	return (_Next);
	}

więc pewnie chodziło ci, że mam się pozbyć całego erase'a

wtedy wszystko ładnie, pięknie działa, tylko, że pamięć też się fajnie bawi. Rośnie jak szalona.

0

Bo erase ma być, tylko zwróć uwagę na lambdę:

 std::remove_if(bulletPart.begin(), bulletPart.end(), [](const Particle* p) //tu najlepiej referencję dać, wskaźnik już nie pasuje :)
0

Przeczytałem sobie trochę o konstruktorach przenoszących: https://kacperkolodziej.pl/programowanie/cpp11-referencje-do-r-wartosci-i-przenoszenie-danych.html. Z tych przykładów co on podawał wynika, że trzeba zrobić 2 obiekty. Trochę mi się to wydaje bezsensowne, chyba, że jest jeszcze inny sposób.

I odnośnie wątków. To źle, że nie używam mutexów?

0

W przypadku Twojej klasy Particle to ten konstruktor nie jest potrzebny właściwie, bo tam masz same floaty i inty. Pisałem o tym zanim zobaczyłem, jak ta klasa wygląda.

Co do mutexów. To zależy. Zależy od tego czy różne wątki używają tych samych danych (w dużym uproszczeniu). Poza tym źle użyte mutexy także wpływają na wydajność. Czasami lepiej użyć jakiejś nieblokującej struktury danych lub kontenera, ale to już bardziej zaawansowany temat.
W twoim kodzie, jeśli dwa wątki używają tych samych danych (np. wektor z cząsteczkami), to przed użyciem użyj lock guarda (najbezpieczniej i najłatwiej).
Mógłbyś jeszcze pokazać, jak tworzysz te wątki i co w nich jest.

0

A jak z tą powiększającą się pamięcią tak o 1MB w przedziale od 20sekund do minuty, kiedy w ogóle się nie ruszam, a zrenderowane są tylko 2/3 obiekty?
Co do klas:
manager


#include "Human.h"
#include "Cleaver.h"
#include "PlayerGun.h"
#include "FileManager.h"

class EntityManager
{
public:
	typedef enum EntityType
	{
		none = 0,
		player = 1,
		human = 2,
		playerGun = 3,
		cleaver = 4,
	};
	EntityType currentType;

	EntityManager();
	~EntityManager();

	void Update();
	void Draw(SDL_Renderer *renderer);

	void AddComponent(std::string name, EntityType type, SDL_Renderer *renderer);
	void AddPlayerGun(int x, int y, int moveDirection, SDL_Renderer *renderer);

	void CreateEntity(std::vector<std::vector<std::string>> map);
	void ClearEntity();

	int getList(int x);

	int getNumOfEntities();

private:
	FileManager file;
	std::vector<std::vector<Entity*>> ent;

	int entitySize;
};
#endif // ! 
#include "EntityManager.h"

EntityManager::EntityManager()
{
	this->currentType = none;
}

EntityManager::~EntityManager()
{
	for (auto &e : entity)
	{
		for (const auto &en : e)
		{
			delete en;
		}
		e.clear();
	}
	entity.clear();
}

void EntityManager::Update()
{
	for (const auto &e : entity)
	{
		for (const auto &en : e)
		{
			if (en->entityState == Entity::eLive)
			{
				en->Update();
			}
		}
	}
	
	for (auto &e : entity)
	{
		auto RemoveIt = std::remove_if(e.begin(), e.end(), [](const Entity* p)
		{
			return p->entityState == Entity::eDead;
		});
		for (auto it = RemoveIt; it != e.end(); ++it)
		{
			delete *it;
		}

		e.erase(RemoveIt, e.end());
	}
}

void EntityManager::Draw(SDL_Renderer * renderer)
{ 
	for (const auto &e : entity)
	{
		for (const auto &en : e)
		{
			if (en->entityState == Entity::eLive)
			{
				en->Draw(renderer);
			}
		}
	}
}

int EntityManager::getList(int x)
{
	return x / 100;
}

int EntityManager::getNumOfEntities()
{
	int num = 0;
	for (const auto &e : entity)
	{
		num += e.size();
	}
	return num;
}

void EntityManager::AddComponent(std::string name, EntityType type, SDL_Renderer *renderer)
{
	std::vector<std::vector<std::string>> attributes;
	std::vector<std::vector<std::string>> contents;

	std::vector<float> eX, eY, eMS, eRG, eMD, cRadius;

	file.LoadFromFile(name, attributes, contents, name);

	for (int i = 0; i < attributes.size(); i++)
	{
		for (int j = 0; j < attributes[i].size(); j++)
		{
			std::string att = attributes[i][j];
			std::string con = contents[i][j];

			float conf = atof(con.c_str());
			int coni = atoi(con.c_str());

			if (att == "x") eX.push_back(conf);
			else if (att == "y") eY.push_back(conf);
			else if (att == "ms") eMS.push_back(conf);
			else if (att == "range") eRG.push_back(conf);
			else if (att == "moveDirection") eMD.push_back(coni);
			else if (att == "radius") cRadius.push_back(coni);
		}	
	}	

	for (int i = 0; i < contents.size(); i++)
	{
		switch (type)
		{
			case human:
				entity[getList(eX[i])].push_back(new Human(eX[i], eY[i], eMS[i], eRG[i], eMD[i], renderer));
				break;
			case cleaver:
				entity[getList(eX[i])].push_back(new Cleaver(eX[i], eY[i], cRadius[i], 0, eMD[i], renderer));
				break;
		}
	}
}

void EntityManager::AddPlayerGun(int x, int y, int moveDirection, SDL_Renderer *renderer)
{
	entity[getList(x)].push_back(new PlayerGun(x,y, moveDirection, renderer));
}

void EntityManager::CreateEntity(std::vector<std::vector<std::string>> map)
{
	for (int y = 0; y < map.size(); y+=5)
	{
		std::vector<Entity*> tempEnt;
		for (int i = 0; i < map[y].size(); i++)
		{
			entity.push_back(tempEnt);
		}
	}

	entitySize = entity.size();
}

void EntityManager::ClearEntity()
{
	for (auto &e : entity)
	{
		for (const auto &en : e)
		{
			delete en;
		}
		e.clear();
	}
}

Tak wywołuje na mapie

void GameplayScreen::LoadMap(SDL_Renderer *renderer)
{
	file.LoadFromFile("Map", map);
	
	entityManager.ClearEntity();
	entityManager.CreateEntity(map);

	entityManager.AddComponent("Human", EntityManager::EntityType::human, renderer);
	entityManager.AddComponent("Cleaver", EntityManager::EntityType::cleaver, renderer);
}

render i update entityManager.Update(); entityManager.Draw(renderer);

całe entity

#pragma once

#ifndef ENTITY_H
#define ENTITY_H

#include "Texture.h"
#include "Vector2.h"
#include "FileManager.h"

class Entity
{
public:
	typedef enum JumpState
	{
		eOnGround = 0,
		eJump = 1,
		eFalling = 2,
	};
	JumpState jumpState;

	typedef enum EntityState
	{
		eDead = -1,
		eLive = 0,
	};
	EntityState entityState;

	Entity();
	~Entity();

	virtual void Draw(SDL_Renderer *renderer);
	virtual void Update();
	virtual void UpdatePosX();
	virtual void UpdatePosY(float moveSpeed);
	virtual void EntityPhysics();
	virtual void moveAnimation(int maxSpriteID);
	virtual void resetJump();

	int getX(), getY();
	int getHitBoxX(), getHitBoxY();

	int getMoveDirection();
	int getRange();
	int getJumpState();

	float getVelX();
	float getVelY();
	float getJumpSpeed();
	float getGravity();

	void setSpriteID(int id);
	int getSpriteID();

	int getBlockID();
protected:
	FileManager file;
	Texture eSprite;

	bool spawnEntity;
	bool bDestroy;

	float posX, posY;
	float hitBoxX, hitBoxY;

	float moveSpeed;
	float velX, velY;

	int spriteSize;
	int spriteID;
	int moveAnim;

	unsigned int passedTime;
	unsigned int moveAnimationTime;

	//-------- jump ---------
	float jumpSpeed;

	int moveDirection;

	int blockID;

	int range, counterRange;
};
#endif // !1

Pokaże tylko te najważniesze metody, bo gettersy i settersy to wiadomo jak wyglądają

Entity::~Entity()
{
	eSprite.free();
}

void Entity::Draw(SDL_Renderer * renderer) {}
void Entity::Update() {}
void Entity::UpdatePosX()
{
	if (moveDirection)
	{
		if ((posX + hitBoxX > 1200) || (range > 0 && counterRange >= range))
		{
			counterRange = 0;
			moveDirection = !moveDirection;
		}
		else
		{
			counterRange += velX;
			posX += velX;
		}
	}
	else
	{
		if ((posX < 10) || range > 0 && counterRange >= range)
		{
			counterRange = 0;
			moveDirection = !moveDirection;
		}
		else
		{
			counterRange += velX;
			posX -= velX;
		}
	}
}

void Entity::UpdatePosY(float moveSpeed) 
{
	if (moveSpeed > 0)
	{
		if (posY + hitBoxY < 500)
		{
			posY += moveSpeed;
		}
		else velY = 0;
	}
	else if (moveSpeed < 0)
	{
		//if ()
		{
			posY += moveSpeed;
		}
		//else velY = 0;
	}
}
void Entity::EntityPhysics()
{
	if (posY + hitBoxY < 500)
	{
		velY += gravity;
		jumpState = eFalling;
	}
	else if (jumpState == eFalling)
	{
		velY = 0;
		jumpState = eOnGround;
	}
	UpdatePosY(velY/jumpFactor);
}

//metoda animacji jest trochę źle napisana, ale działa
void Entity::moveAnimation(int maxSpriteID)
{
	if (moveAnim)
	{
		if (SDL_GetTicks() - 100 - velX >= moveAnimationTime)
		{
			moveAnimationTime = SDL_GetTicks();
			if (spriteID >= maxSpriteID)
			{
				setSpriteID(0);
			}
			else spriteID++;
		}
	}
}

Gravity i jumpFactor określiłem w jednym pliku jako
define gravity 3
define jumpFactor 3.2
żeby dla kazdej klasy, która będzie korzystac z grawitacji nie trzeba bylo robic nowych zmiennych, też nie wiem czy to dobry pomysł, dlatego piszę.

Dla przykładu pokaze tylko jedną klasę, która dziedziczy, bo po co więcej

#pragma once
#ifndef  HUMAN_H
#define HUMAN_H

#include "Entity.h"

class Human : public Entity
{
public:
	Human(int x, int y, int ms, int rg, int moveDirection, SDL_Renderer *renderer);
	~Human();

	void Draw(SDL_Renderer *renderer);
	void Update();	
private:
};
#endif // ! HUMAN_H
#include "Human.h"

Human::Human(int x, int y, int ms, int rg, int moveDirection, SDL_Renderer *renderer)
{
	this->spriteID = 0;

	this->moveDirection = moveDirection;

	this->posX = x;
	this->posY = y;

	this->range = rg;

	this->velX = ms;
	this->velY = 0;

	this->counterRange = 0;

	this->hitBoxX = 30;
	this->hitBoxY = 57;

	eSprite = Texture("Human/humanSprites", renderer);

	spriteSize = (int)(eSprite.getWidth() / 33) - 1;

	if (velX > 0) moveAnim = true;
}

Human::~Human()
{
        Entity::~Entity();
	//eSprite.free();
}

void Human::Draw(SDL_Renderer* renderer)
{
	if (entityState != eDead)
	{
		SDL_Rect humanDestRect;
		humanDestRect.x = spriteID * 32;
		humanDestRect.y = 0;
		humanDestRect.w = 32;
		humanDestRect.h = 58;

		SDL_Rect humanRect;
		humanRect.x = posX;
		humanRect.y = posY;
		humanRect.w = 32;
		humanRect.h = 57;

		eSprite.Draw(humanDestRect, humanRect, renderer, !moveDirection);
	}
}

void Human::Update()
{
	if (posY > 1000)
	{
		entityState = eDead;
	}	

	if (entityState == eLive)
	{
		moveAnimation(spriteSize);
		EntityPhysics();
		UpdatePosX();
	}
}

W destrukorze pojawia się eSprite->free();
tutaj ono:

void Texture::free()
{
	mTexture = NULL;
	SDL_DestroyTexture(mTexture);
}

Nie wiem w sumie dlaczego przy każdym enumie dawałem typedefa skoro nazwy nie są jakoś długie, ale to chyba nic nie zmienia.
tutaj jeszcze plik z którego czytam Humana

Human:{x, y, ms, range, moveDirection}
39, 500, 1, 250, 0
300, 500, 3, 0, 1
Human;

Na koniec chciałbym jeszcze bardzo mocno podziękować za czas, który poświęciliście dla mnie i za każdą cenną radę, którą będę mógł wykorzystać w przyszłości.

0

Tak na szybko (jak będę miał więcej czasu to przejrzę resztę):
1.

void Texture::free()
{
    mTexture = NULL;
    SDL_DestroyTexture(mTexture);
}

Zamień linijki, bo do DestroyTexture wrzucasz NULL, więc zapewne nic to nie robi. Podejrzewam więc wyciek pamięci tutaj.

  1. W każdej klasie bazowej należy zdefiniować wirtualny destruktor. Inaczej, jeśli będziesz korzystał z klas pochodnych za pomocą wskaźnika do klasy bazowej, to po ich zniszczeniu destruktor klas pochodnych się nie wywoła.

Polecam też lekturę na dobranoc: wszystkie książki z serii Efective X Meyersa, co byś sobie dobre praktyki przyswoił.
Jeśli chodzi o wielowątkowość, to solidne podstawy będą tu : https://helion.pl/ksiazki/jezyk-c-i-przetwarzanie-wspolbiezne-w-akcji-anthony-williams,jcpppw.htm#format/d

0

Jeszcze jedno mam takie pytanie, bo jak spamuje sobie pociskami to po chwili cały program się kraszuje
screenshot-20171107122736.png

Czy w ogóle powinno tam być

	for (auto i = removeIf; i != e.end(); ++i)
		{
			delete (*i);
		}

?
tak mi w sumie podawałeś w kodzie, dlatego się pytam.

0

A czy tworzenie nowych i update pocisków odbywa się w osobnych wątkach?

0

Nie wiem co może powodować ten błąd. Trzeba by to zdebugować i krok po kroku prześledzić jak kod jest wykonywany. Na załączonym screenie widać jedynie, że crashuje, gdy chcesz usunąć pointer trzymany przez iterator RemoveIf. Zauważyłem też, żę masz tam jakieś śmieci w tym konkretnym particle'u. Zakładam oczywiście, że build debugowy jest, tak?
Możliwe przyczyny (ale upewnij się jeszcze, że budujesz w trybie Debug - to istotne):

  • próbujesz usunąć już usunięty obiekt (w takiej sytacji możesz spróbować po każdym delete ustawić usunięty wskaźnik na null)
  • usuwasz wskaźnik, który nie został zainicjalizowany prawidłowym obiektem (stąd śmieci), tylko wskazuje na jakiś losowy adres
  • usuwasz wskaźnik, który nie wskazuje na obiekt zaalokowany na stercie (za pomocą new)

Zwróć jeszcze uwagę, czy crash występuje w losowych sytuacjach, czy zawsze w tym samym momencie (np. zaraz po stworzeniu nowej cząsteczki). Pokaż jeszcze główną pętlę gry, jak zbierasz input ( zakładam, że jakiś klawisz wyzwala tworzenie particle'a) oraz główną funkcję update. Jeśli masz wątki, pokaż jak je tworzysz oraz pokaż funkcje, które wywołujesz wielowątkowo.

0
#include "Core.h"
#include "Config.h"

bool Core::quit = false;

Core::Core() 
{
	SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_AUDIO);

	setScreenRes();

	this->window = SDL_CreateWindow("Platformer", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, file.getW(), file.getH(), SDL_WINDOW_RESIZABLE);

	this->renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);

	SDL_RenderSetLogicalSize(renderer, Config::SCREEN_WIDTH, Config::SCREEN_HEIGHT);

	this->quit = false;

	if (window == NULL)
	{
		this->quit = true;
	}

	this->event = new SDL_Event;

	this->frameTime = 0;
	this->MIN_FRAME_TIME = 16.666666666f;

	LoadContent();
}

Core::~Core()
{
	delete event;
	SDL_DestroyWindow(window);
	SDL_DestroyRenderer(renderer);
	SDL_ClearHints();
	Mix_Quit();
	SDL_Quit();
	IMG_Quit();
}

void Core::setScreenRes()
{
	file.LoadFromFile("Video");
	Config::setWidth(file.getW());
	Config::setHeight(file.getH());

	//SDL_SetHint(SDL_HINT_RENDER_DRIVER, "software");
}

void Core::MainLoop()
{
	while (!quit && event->type != SDL_QUIT)
	{
		frameTime = SDL_GetTicks();

		SDL_PollEvent(event);

		SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
		SDL_RenderClear(renderer);

		Input();

		Update();

		Draw();

		SDL_RenderPresent(renderer);

		if (SDL_GetTicks() - frameTime < MIN_FRAME_TIME)
		{
			SDL_Delay(MIN_FRAME_TIME - (SDL_GetTicks() - frameTime));
		}
	}
}

void Core::Draw()
{
	ScreenManager.Draw(renderer);
}
void Core::Update()
{
	input.Update(*event);
	ScreenManager.Update();
}

void Core::LoadContent()
{
	ScreenManager.setActiveTexture(renderer);
	ScreenManager.LoadContent(renderer);
}

void Core::Input()
{
	switch (ScreenManager.GetGameState())
	{
		case 3: input.PlayerInput(renderer); break;
		case 6: input.LevelInput(); break;
		default: input.MenuInput(); break;
	}
}

Tak wiem, licznik fpsów jest tragiczny, ale nie znałem wtedy lepszego sposobu, miałem poczytać trochę o tym, ale mi się nie chciało i ciągle odkładałem na inną chwilę.

input

if (mouseState == mouseLeft)
	{
		ePlayer()->GunOut(renderer);
		mouseLeft = true;
	}
0

Tutaj wygląda w porządku. Jeszcze mógłbyś pokazać ScreenManager. Mam jescze pytanie, czy update i destruktor to jedyne miejsca, gdzie wołasz delete na particle'ach?
Mimo wszystko spróbowałbym jednak zamiast gołych pointerów trzymać tam unique pointery. Będzie to mniej błędogenne. Ręczna alokacja wymaga sporej dyscypliny. Mnie jest na tym etapie ciężko coś więcej powiedzieć, gdyż nie mam poglądu na całość projektu i nie mogę go zdebugować.

0

Tak update i destruktor to jedyne miejsca.

Gdyby te smart pointery mogły naliczać do inta kiedy jakiś entity pada. Nie widać tego ze się usuwa, trochę jak z Bogiem, też go nie widać. A co do nich, są jakieś fancy bajery które w update powinienem robić czy usunąć wszystko i zostawić tylko entity->Update()?

#pragma once
#ifndef SCREENMANAGER_H
#define SCREENMANAGER_H

#include "SplashScreen.h"
#include "MainMenu.h"
#include "OptionsScreen.h"
#include "GameplayScreen.h"
#include "PauseScreen.h"
#include "LevelEditor.h"

class ScreenManager
{
public:
	ScreenManager();
	~ScreenManager();

	enum GameState
	{
		gsMainMenu,
		gsLoadingMenu,
		gsGame,
		gsOptionsMenu,
		gsPauseMenu,
		gsLe,
	};
	GameState currentScreen;

	void LoadContent(SDL_Renderer *renderer);
	void Draw(SDL_Renderer *renderer);
	void Update();
	void resetActiveTexture(GameState id);
	void keyPressed(int key);
	void Enter();
	void Escape();
	void playerInput(SDL_Renderer *renderer, SDL_Event event);

	void SetGameState(GameState gameState);
	int GetGameState();

	OptionsScreen *GetOptionsScreen();
	GameplayScreen *GetGame();

private:
	MainMenu *mainMenu;
	OptionsScreen *optionsScreen;
	GameplayScreen *Game;
	PauseScreen *pauseScreen;
	LevelEditor *le;
};
#endif
#include "ScreenManager.h"

ScreenManager::ScreenManager()
{
	this->currentScreen = gsMainMenu;
}

ScreenManager::~ScreenManager()
{
	delete mainMenu;
	delete Game;
	delete optionsScreen;
	delete pauseScreen;
	delete le;
}
void ScreenManager::LoadContent(SDL_Renderer *renderer)
{
	mainMenu = new MainMenu(renderer);
	optionsScreen = new OptionsScreen(renderer);
	Game = new GameplayScreen(renderer);
	pauseScreen = new PauseScreen(renderer);
	le = new LevelEditor(renderer);
}
void ScreenManager::Draw(SDL_Renderer *renderer)
{
	switch (currentScreen)
	{
		case gsMainMenu: mainMenu->Draw(renderer); break;
		case gsOptionsMenu: optionsScreen->Draw(renderer); break;
		case gsGame: Game->Draw(renderer); break;
		case gsPauseMenu: pauseScreen->Draw(renderer); break;
		case gsLe: le->Draw(renderer); break;
	}
}
void ScreenManager::Update()
{
	switch (currentScreen)
	{
		case gsMainMenu: mainMenu->Update(); break;
		case gsOptionsMenu: optionsScreen->Update(); break;
		case gsGame: Game->Update(); break;
		case gsPauseMenu: pauseScreen->Update(); break;
	}
}

void ScreenManager::SetGameState(GameState gameState)
{
	this->currentScreen = gameState;
}

int ScreenManager::GetGameState()
{
	return currentScreen;
}

void ScreenManager::keyPressed(int key)
{
	switch (currentScreen)
	{
                // tutaj są możliwe 4 klawiszę, 
                // dla inta = 0 lub 1 -> w lub s 
                // dla inta = 2 lub 3 -> a lub d
                // nie jest to najszybsze ani najlepsze rozwiązanie, panuje to też zmienić niedługo
                // ale na razie jak działa to niech działa.
		case gsMainMenu: mainMenu->UpdateActiveOptions(key); break;
		case gsOptionsMenu: optionsScreen->UpdateActiveOptions(key); break;
		case gsPauseMenu: pauseScreen->UpdateActiveOptions(key); break;
	}
}

void ScreenManager::Enter()
{
	switch (currentScreen)
	{
		case gsMainMenu: mainMenu->Enter(); break;
		case gsOptionsMenu: optionsScreen->Enter(); break;
		case gsPauseMenu: pauseScreen->Enter(Game->getPlayer()->getPosX(), Game->getPlayer()->getPosY(), Game->getPosX(), Game->getPosY()); break;
	}
}

void ScreenManager::Escape()
{
	switch (currentScreen)
	{
		case gsPauseMenu: pauseScreen->Escape(); break;
		case gsMainMenu: mainMenu->Escape(); break;
	}
}

void ScreenManager::playerInput(SDL_Renderer * renderer, SDL_Event event)
{
	bool keyPressed = false;
	if (event.type == SDL_WINDOWEVENT)
	{
		switch (event.window.event)
		{
		case SDL_WINDOWEVENT_FOCUS_LOST:
			//pauseScreen->activeMenuOptions = 0;
			//currentScreen = gsPauseMenu;
			break;
		}
	}

	if (event.type == SDL_KEYUP)
	{
		switch (event.key.keysym.sym)
		{
		case SDLK_KP_ENTER: case SDLK_RETURN: case SDLK_ESCAPE: keyPressed = false; break;
		}
	}
	else if (event.type == SDL_KEYDOWN)
	{
		switch (event.key.keysym.sym)
		{
		case SDLK_KP_ENTER: case SDLK_RETURN:
			if (!keyPressed)
			{
				Enter();
				keyPressed = true;
			}
			break;
		case SDLK_ESCAPE:
			if (!keyPressed && currentScreen == gsGame)
			{
				pauseScreen->activeMenuOptions = 0;
				currentScreen = gsPauseMenu;
				keyPressed = true;
			}
			break;
		}
	}
	Game->PlayerInput(renderer);
}

OptionsScreen *ScreenManager::GetOptionsScreen()
{
	return optionsScreen;
}

GameplayScreen *ScreenManager::GetGame()
{
	return Game;
}
0

Gdyby te smart pointery mogły naliczać do inta kiedy jakiś entity pada. Nie widać tego ze się usuwa, trochę jak z Bogiem, też go nie widać.

Unique pointer to żadna filozofia, zasada działania jest trywialna. Sam mógłbyś coś takiego w 5 minut napisać.
Co do naliczania, to nie bardzo rozumiem - mógłbyś rozwinąć?

A co do tego, kiedy entity jest usuwane, to wówczas odpala się destruktor. Wiesz, że tam sobie możesz jakiś kod dodać?

0

A co do tego, kiedy entity jest usuwane, to wówczas odpala się destruktor. Wiesz, że tam sobie możesz jakiś kod dodać? Gdzie dodać kod? Do destruktora? No to wiem że do destruktora moge.
Chodziło mi o to, że jak w normalnych pointerach był ten fancy zapis z remove_if, to może dla smart pointerów też coś podobnego jest.

Co do tego naliczania to chodzi mi o to

int EntityManager::getNumOfEntities()
{
	int num = 0;
	for (const auto &e : entity)
	{
		num += e.size();
	}
	return num;
}

wpisywałem sobie na ekranie gry ile jest aktualnie zrenderowanych entity i jak jakieś entity umierało/wychodziło za dystans i stawało się entityState == Entity::eDead , na ekranie liczba też malała o ilość tego entity. Albo kiedy dodawałem jakieś entity, np. pocisk to liczba rosła.

0

Ależ remove_if zostaje. remove_if nie usuwa żadnych elementów wbrew temu, co sugeruje nazwa, tylko sortuje kontener w ten sposób, że elementy spełaniające warunek są przesuwane na koniec. Następnie zwraca iterator na pierwszy z elementów do usunięcia. Później trzeba wywołać erase dla zakresu zwrócony iterator - end.

Jedyne co zniknie to ta pętla z delete'ami, bo jak będziesz miał smart pointery, to w trakcie erase same zwolnią pamięć.

0

Zrobione! Tylko karwa nadal ta pamięc się zwiększa co parę sekund. Może ja coś mam z mapą zamiast z entity.

Żeby nie było błędu jak w ostatniej publikacji kodu
.h https://pastebin.com/szNz9Ywf
.cpp https://pastebin.com/Vh2cx4Hx

0

Nie pozostaje nic innego jak odpalić w profilerze. W visual studio: Debug -> Performance Profiler
I tam masz coś co się nazywa heap profiling. Musisz sobie na początku zrobić snapshot i później jak zużycie pamięci urośnie zrobić drugi. Potem zostaje analiza :)

0

Widzę. W VS17 jest to taki mały obrazek z długim napisem, dlatego nie widziałem. Okazuje się, że to update tak dużo zabiera. Tylko zastanawia mnie dlaczego. Nie mam tam kolizji, nie mam tam jakichś zaawansowanych obliczeń... proste pos += vel i 2 ify...

0

Sprawdź ile masz tych cząstek w wektorze jak Ci tak pamięć rośnie. Jeśli nie za dużo, to obstawiałbym, że się tekstura nie zwolniła. Chociaż nie napisałeś jak bardzo ta pamięć rośnie. Ile jest na początku a ile później?
Jeżeli zaś ilość cząstek stale rośnie, tj. nie są one usuwane, to może ustawianie flagi eDead nie działa tak jak powinno?

0

Każda kula ma 130 cząsteczek, które zerują się kiedy pocisk zniknie ( zaczynając od 5, tak długo jak pocisk jest w locie dodaje się 5 cząstek). Flaga eDead działa na pewno dobrze. Teraz, gdy usunąłem update w jednej klasie entity - Human to pamięć już tak szybko nie rośnie. Raczej muszę z 2 minuty albo nawet więcej poczekać żeby o 1MB urosła. To już jest chyba taka norma. Chociaż nie wiem. Może powinna stać w miejscu.

1 użytkowników online, w tym zalogowanych: 0, gości: 1