Ładowanie obiektu z pliku binarnego

0

Witam
Mam taki problem że kiedy tworze plik binarny za pomocą klasy fstream i chciałbym zapisać w nim obiekt typu sf::RectangleShape z sfmla, a następnie odczytać i wyświetlić, visual studio nie sygnalizuję żadnego błędu. Jeśli próbuje wyświetlić w konsoli jakiś parametr obiektu wczytanego za pomocą fstream bez problemowo wszystko działa. Problem się pojawia kiedy próbuje wyświetlić ten obiekt. Próbowałem dynamicznie zaalokować obiekt a później skopiować ten odczytany ale to nie działa.

#include <SFML/Graphics.hpp>
#include <iostream>
#include <fstream>

bool isCollision(sf::Vector2f A_pos, sf::Vector2f A_size, sf::Vector2f B_pos, sf::Vector2f B_size) {
	if (A_pos.x + A_size.x >= B_pos.x && A_pos.x <= B_pos.x + B_size.x && A_pos.y + A_size.y >= B_pos.y && A_pos.y <= B_pos.y + B_size.y) {
		return true;
	}

	return false;
}


int main() {

	sf::RenderWindow window(sf::VideoMode(300,300), "Snake", sf::Style::Default);
	window.setFramerateLimit(100);

	sf::RectangleShape rect;
	rect.setFillColor(sf::Color::Blue);
	rect.setPosition(sf::Vector2f(20,85));
	rect.setSize(sf::Vector2f(20,100));

	using namespace std;

    //Jesli stworze plik to juz tego kodu ponizej nie potrzebuje
	/*
	fstream saveFile("plik.bin", ios_base::binary | ios_base::out);
	saveFile.write((char*)&rect, sizeof(sf::RectangleShape));
	saveFile.close();
	*/



	sf::RectangleShape rec;

	fstream plik("plik.bin", ios_base::binary | ios_base::in);
	if (plik.good()) {
		cout << "Znalazlo plik" << endl;
		plik.read((char*)&rec, sizeof(sf::RectangleShape));
		plik.close();
	}



	while (window.isOpen()) {
		sf::Event event;

		while (window.pollEvent(event)) {
			if (event.type == sf::Event::Closed) {
				window.close();
			}
		}
		if (isCollision(sf::Vector2f(sf::Mouse::getPosition().x-window.getPosition().x, sf::Mouse::getPosition().y - window.getPosition().y), sf::Vector2f(1, 1), rect.getPosition(), rect.getSize())) {
			rect.setFillColor(sf::Color(rand()+ 255, rand() + 255,rand() + 255));
		}
		using namespace std;	


		window.clear();


        //Ta linijka ponizej generuje blad 
        window.draw(rec);
		window.display();

	}

	return 0;
}
4

Prawdopodobnie obiekt ma część dynamiczną, czyli wskaźniki lub klasy zawierające część dynamiczną,
takie obiekty nie da się zapisać wprost.

2

Od formalnej strony ten kod

plik.read((char*)&rec, sizeof(sf::RectangleShape));

Zawiera undefined behavior.
Zwykle zadziała, ale mogą się podziać różne dziwne rzeczy.

Wiele ludzi tak robi z lenistwa (bo to jest szybki hack, który się wydaje działać zawsze).

Zalecałbym użyc jakiejś biblioteki i zapisać / odczytać dane ozywając jakiegoś standardowego formatu przyjaznego człowiekowi: JSon, xml, YAML?
Po pierwsze nauczysz się jakiś standardów zapisywania danych; po drugie: będziesz miał plik, który będzie łatwy w poprawianiu i czytaniu.

Równocześnie dziel kod na małe funkcje. Oddzielaj wczytywanie danych, od obłsugi UI, od logiki aplikacji, itp itd ... .
To ograniczy spaghetti code.

sf::RectangleShape ma dużo więcej niż tylko wymiary. Trzyma nawet teksturę.
Ergo używa wskaźników. Nigdy nie wolno zapisywać dosłownie wskaźników by potem je odczytać przy kolejnym uruchomieniu. A to włąśnie robisz.

0
MarekR22 napisał(a):

Od formalnej strony ten kod

plik.read((char*)&rec, sizeof(sf::RectangleShape));

Zawiera undefined behavior.
Zwykle zadziała, ale mogą się podziać różne dziwne rzeczy.

Wiele ludzi tak robi z lenistwa (bo to jest szybki hack, który się wydaje działać zawsze).

Zalecałbym użyc jakiejś biblioteki i zapisać / odczytać dane ozywając jakiegoś standardowego formatu przyjaznego człowiekowi: JSon, xml, YAML?
Po pierwsze nauczysz się jakiś standardów zapisywania danych; po drugie: będziesz miał plik, który będzie łatwy w poprawianiu i czytaniu.

Równocześnie dziel kod na małe funkcje. Oddzielaj wczytywanie danych, od obłsugi UI, od logiki aplikacji, itp itd ... .
To ograniczy spaghetti code.

sf::RectangleShape ma dużo więcej niż tylko wymiary. Trzyma nawet teksturę.
Ergo używa wskaźników. Nigdy nie wolno zapisywać dosłownie wskaźników by potem je odczytać przy kolejnym uruchomieniu. A to włąśnie robisz.

to jak to rozwiązać ?

0

@plugan300: jak Ty chcesz zapisać dosłownie wskaźnik? Piszesz o wartości na którą wskazuje? Nie wolno zapisać wartości na którą wskazuje wskaźnik?

0
plugan300 napisał(a):

to jak to rozwiązać ?

MarekR22 napisał(a):

Zalecałbym użyc jakiejś biblioteki i zapisać / odczytać dane ozywając jakiegoś standardowego formatu przyjaznego człowiekowi: JSon, xml, YAML?
Po pierwsze nauczysz się jakiś standardów zapisywania danych; po drugie: będziesz miał plik, który będzie łatwy w poprawianiu i czytaniu.

0
Adamek Adam napisał(a):

https://json.nlohmann.me/api/adl_serializer/to_json/#examples
https://json.nlohmann.me/api/adl_serializer/from_json/#examples

Chciałem kreator mapy do gry 2d gdzie używam własnych klas w których są obiekty z sfml i inne dane, myślałem że wrzuce wszystko do pliku binarnego i łatwo odczytam. A w jaki sposób powinno się zapisywać takie dane ?

0
MarekR22 napisał(a):
plugan300 napisał(a):

to jak to rozwiązać ?

MarekR22 napisał(a):

Zalecałbym użyć jakiejś biblioteki i zapisać / odczytać dane ozywając jakiegoś standardowego formatu przyjaznego człowiekowi: JSon, xml, YAML?
Po pierwsze nauczysz się jakiś standardów zapisywania danych; po drugie: będziesz miał plik, który będzie łatwy w poprawianiu i czytaniu.

w linkach masz przykład z "user-defined type ns::person" w obie strony do json i z json. Dodajesz funkcje from_json , to_json i gotowe ;)
Oczywiście jak obiekt jest skompilowany to trzeba trochę więcej napisać niż w przykładach.

1

Jeśli używasz C++17 to da się uzyskać serializację/deserializację każdej struktury/klasy bez pisania dodatkowego kodu - tj. funkcji dla każdej klasy osobno.

https://github.com/CppCon/CppCon2022/blob/main/Presentations/Killing-C%2B%2B-Serialization-Overhead-%26-Complexity-EyalZedaka.pdf

1

RectangleShape dziedziczy po Shape a tam takie coś:

class SFML_GRAPHICS_API Shape : public Drawable, public Transformable
{
....
private:
....
    const Texture* m_texture{};                  //!< Texture of the shape
    IntRect        m_textureRect;                //!< Rectangle defining the area of the source texture to display
    Color          m_fillColor{Color::White};    //!< Fill color
    Color          m_outlineColor{Color::White}; //!< Outline color
    float          m_outlineThickness{};         //!< Thickness of the shape's outline
    VertexArray    m_vertices{PrimitiveType::TriangleFan};          //!< Vertex array containing the fill geometry
    VertexArray    m_outlineVertices{PrimitiveType::TriangleStrip}; //!< Vertex array containing the outline geometry
    FloatRect      m_insideBounds;                                  //!< Bounding rectangle of the inside (fill)
    FloatRect      m_bounds; //!< Bounding rectangle of the whole shape (outline + fill)
};

m_texture jest wskaźnikiem ale jak tekstur nie używasz to jest tam null_ptr i do pliku zapiszą się zera, więc to jeszcze przejdzie.

Problem jest pewnie z VertexArray który zawiera pole std::vector<Vertex> m_vertices;, a tam jak wiadomo również jest wskaźnik

Wywołaj po kolei getFillColor, getPosition, getSize dla obiektu rect i te wartości dopiero zapisz do pliku binarnego. To są proste typy i nie zawierają wskaźników więc zrzut pamięci nie przysporzy problemów.

0

m_texture też nie przejdzie - bo zapis binarny nie analizuje pół, tylko zapisuje zrzut pamięci spod danego adresu. Tak jak pisali poprzednicy - takie zrzuty pamięci są 1) niebezpieczne (jeśli nie wiesz co robisz), 2 często nieprzenośne - wystarczy, że kompilacja będzie z innymi parametrami i może się okazać, że dane się rozjadą. Dlatego do takich celów używa się, tak jak pisali to wcześniej, albo czytelnych dla użyszkdnika formatów jak xml i pochodne, albo binarnych, ale w ustandaryzowany sposób, gdzie czytamy blokami (jak np stary format iff)

0

/Napisz może to po ludzku.
Robisz wskaźnik na char, ustawiasz na pierwszym elemencie struktury, zapisujesz do pliku tyle bajtów jaki jest rozmiar(przesuwając wskaźnik, czyli w pętli). Potem następny element struktury i tak dalej.
Żeby odczytać tworzysz strukturę, tworzysz wskaźnik ustawiasz na początku pierwszego elementu i bajt bo bajcie odczytujesz z pliku tyle jaki jest jego rozmiar. To samo robisz z następnym.

Przykład:

#include <stdio.h>
struct typek{
            int pierwszy;
            double drugi;
            };

int main(){
          FILE *plik;
          plik=fopen("aplik.txt","wb");
          struct typek nowy;
          nowy.pierwszy=555;
          nowy.drugi=33;
          char *tmp;


/*  zapisywanie */


/* ustawiasz na poczatku zmiennej */

          tmp=(char*)&nowy.pierwszy; 

 /* jedziesz wskaźnikiem po wartościach w zmiennej nowy.pierwszy */
 
for(int i=0;i<sizeof(nowy.pierwszy);i++){

          fprintf(plik,"%c",*tmp);tmp++;}

/*  powtarzasz operacje */

          tmp=(char*)&nowy.drugi;

/* jedziesz wskaźnikiem po wartościach w zmiennej nowy.drugi */
          
          for(int i=0;i<sizeof(nowy.drugi);i++){
              fprintf(plik,"%c",*tmp);tmp++;}

          fclose(plik);

/*         odczytywanie   */

          struct typek nowszy;

          plik=fopen("aplik.txt","rb");

          tmp=(char*)&nowszy.pierwszy; 

          for(int i=0;i<sizeof(nowszy.pierwszy);i++){
          fscanf(plik,"%c",tmp);tmp++;}	
	


          tmp=(char*)&nowszy.drugi; 

          for(int i=0;i<sizeof(nowszy.drugi);i++){
          fscanf(plik,"%c",tmp);tmp++;}	
	


          fclose(plik);

          printf("Wczytane zmienne:\n%d - %f",nowszy.pierwszy,nowszy.drugi);


return 0;
}

Odczytujesz podobnie.
Jak masz ileś takich samych struktur to robisz pętlę.
Tu się nie ma co nie udać, to są same podstawy.
Nie ma się nad czym modlić.
Wstaw sobie inne liczby niż 555 i 33 (albo zmodyfikuj typy w strukturze) i sam się przekonaj.
Nie wiem w sumie czy by nie dało rady z całą strukturą tak zrobić od razu(sizeof(typek)) i skrócić kod do 5% tego co jest.

0
johnny_Be_good napisał(a):

/Napisz może to po ludzku.

To co podałeś nie jest po ludzku! Sam oceń, to jest po ludzku:

#include <stdio.h>

struct Data
{
    int iVale;
	double dValue;
};

int main()
{
    struct Data data={555,33};
	FILE *fwd=fopen("aplik.txt","wb");
    fwrite(&data,sizeof(data),1,fwd);
    fclose(fwd);
    printf("Zapisane dane: {%d, %f}\n",data.iVale,data.dValue);
    
    struct Data newdata={0,0};
	FILE *frd=fopen("aplik.txt","rb");
    fread(&newdata,sizeof(data),1,frd);
    fclose(frd);    
    printf("Wczytane dane: {%d, %f}\n",newdata.iVale,newdata.dValue);
    
	return 0;
}
0
kaczus napisał(a):

m_texture też nie przejdzie - bo zapis binarny nie analizuje pół, tylko zapisuje zrzut pamięci spod danego adresu. Tak jak pisali poprzednicy - takie zrzuty pamięci są 1) niebezpieczne (jeśli nie wiesz co robisz), 2 często nieprzenośne - wystarczy, że kompilacja będzie z innymi parametrami i może się okazać, że dane się rozjadą. Dlatego do takich celów używa się, tak jak pisali to wcześniej, albo czytelnych dla użyszkdnika formatów jak xml i pochodne, albo binarnych, ale w ustandaryzowany sposób, gdzie czytamy blokami (jak np stary format iff)

Może źle się wyraziłem, m_texture przejdzie tylko w przypadku gdy nie używamy tekstur, wtedy to pole zawiera null_ptr i zapis zera do pliku nie powinien przysporzyć większych problemów. Oczywiście jest to niebezpieczne i tak się nie powinno zapisywać danych ale program przez 99% czasu może chodzić i wykrzaczyć się w najbardziej niekorzystnym momencie. Natomiast zrzut pola m_vertices, który jest typem std::vector<?> gwarantuje że za każdym razem ten program się wysypie, co nawet jest korzystne bo od razu pojawia się błąd, który trzeba poprawić żeby ruszyć dalej z projektem.

Poniżej kod po poprawce. Do pliku trzeba zrzucić potrzebne dane, które pozwalają odtworzyć obiekt:

#include <SFML/Graphics.hpp>
#include <fstream>
#include <iostream>

bool isCollision(sf::Vector2f A_pos, sf::Vector2f A_size, sf::Vector2f B_pos,
                 sf::Vector2f B_size) {
    if (A_pos.x + A_size.x >= B_pos.x && A_pos.x <= B_pos.x + B_size.x &&
        A_pos.y + A_size.y >= B_pos.y && A_pos.y <= B_pos.y + B_size.y) {
        return true;
    }

    return false;
}

int main() {
    sf::RenderWindow window(sf::VideoMode(300, 300), "Snake", sf::Style::Default);
    window.setFramerateLimit(100);

    sf::RectangleShape rect;
    rect.setFillColor(sf::Color::Blue);
    rect.setPosition(sf::Vector2f(20, 85));
    rect.setSize(sf::Vector2f(20, 100));

    using namespace std;

    // Jesli stworze plik to juz tego kodu ponizej nie potrzebuje
    /* fstream saveFile("plik.bin", ios_base::binary | ios_base::out); */
    /* auto fcolor = rect.getFillColor(); */
    /* saveFile.write((char*)&fcolor, sizeof(fcolor)); */
    /* auto pos = rect.getPosition(); */
    /* saveFile.write((char*)&pos, sizeof(pos)); */
    /* auto size = rect.getSize(); */
    /* saveFile.write((char*)&size, sizeof(size)); */
    /* saveFile.close(); */

    sf::RectangleShape rec;

    fstream plik("plik.bin", ios_base::binary | ios_base::in);
    if (plik.good()) {
        cout << "Znalazlo plik" << endl;
        sf::Color fcolor;
        plik.read((char*)&fcolor, sizeof(fcolor));
        sf::Vector2f pos;
        plik.read((char*)&pos, sizeof(pos));
        sf::Vector2f size;
        plik.read((char*)&size, sizeof(size));
        plik.close();

        rec.setFillColor(fcolor);
        rec.setPosition(pos);
        rec.setSize(size);
    }

    while (window.isOpen()) {
        sf::Event event;

        while (window.pollEvent(event)) {
            if (event.type == sf::Event::Closed) {
                window.close();
            }
        }
        if (isCollision(
                sf::Vector2f(
                    sf::Mouse::getPosition().x - window.getPosition().x,
                    sf::Mouse::getPosition().y - window.getPosition().y),
                sf::Vector2f(1, 1), rect.getPosition(), rect.getSize())) {
            rect.setFillColor(
                sf::Color(rand() + 255, rand() + 255, rand() + 255));
        }
        using namespace std;

        window.clear();

        // Ta linijka ponizej generuje blad
        window.draw(rec);
        window.display();
    }

    return 0;
}

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