The Cherno
The Cherno
  • Видео 844
  • Просмотров 74 337 945
Serialization in Hazel - My Game Engine
To try everything Brilliant has to offer-free-for a full 30 days, visit brilliant.org/TheCherno . You’ll also get 20% off an annual premium subscription.
Patreon ► patreon.com/thecherno
Instagram ► thecherno
Twitter ► thecherno
Discord ► discord.gg/thecherno
Hazel ► hazelengine.com
🕹️ Play our latest game FREE (made in Hazel!) ► studiocherno.itch.io/dichotomy
🌏 Need web hosting? ► hostinger.com/cherno
📚 CHAPTERS
0:00 - Intro
0:36 - Keep it simple.
7:48 - API overview
10:27 - Code walkthrough
18:55 - Real-world usage examples
21:22 - From scratch example
26:05 - Concerns
27:02 - Serializing to memory buffers
💰 Links to stuff I use:
⌨ Keyboard ► geni.us/T2J7
🐭 Mouse ► geni.us/BuY7
💻 M...
Просмотров: 17 999

Видео

WHY did this C++ code FAIL?
Просмотров 177 тыс.21 день назад
To try everything Brilliant has to offer-free-for a full 30 days, visit brilliant.org/TheCherno . You’ll also get 20% off an annual premium subscription. Patreon ► patreon.com/thecherno Instagram ► thecherno Twitter ► thecherno Discord ► discord.gg/thecherno CODE ► github.com/luizfelipemb/OresSDL Hazel ► hazelengine.com 🕹️ Play our latest game FREE (made in Hazel!) ► s...
Make C++ Apps & Games FOR THE WEB
Просмотров 44 тыс.21 день назад
Use code CHERNO for a limited time to get an additional discount for all yearly plans! ► hostinger.com/cherno Patreon ► patreon.com/thecherno Instagram ► thecherno Twitter ► thecherno Discord ► discord.gg/thecherno raylib ► www.raylib.com/ raylib itch.io deployment ► github.com/raysan5/raylib/wiki/Working-for-Web-(HTML5)#7-upload-raylib-web-game-to-itchio Genesis Remak...
Harder Than It Seems? 5 Minute Timer in C++
Просмотров 146 тыс.Месяц назад
To try everything Brilliant has to offer-free-for a full 30 days, visit brilliant.org/TheCherno . You’ll also get 20% off an annual premium subscription. Patreon ► patreon.com/thecherno Instagram ► thecherno Twitter ► thecherno Discord ► discord.gg/thecherno Timer thread ► cplusplus.com/forum/beginner/99555/ Why I don't "using namespace std" ► ruclips.net/video/4NYC-VU...
The WORST BUG in my Game Engine
Просмотров 42 тыс.Месяц назад
🚀 Take control of your schedule and boost your productivity with time-blocking! Try Akiflow today: bit.ly/cherno-free-trial Patreon ► patreon.com/thecherno Instagram ► thecherno Twitter ► thecherno Discord ► discord.gg/thecherno Hazel ► hazelengine.com 🕹️ Play our latest game FREE (made in Hazel!) ► studiocherno.itch.io/dichotomy 🌏 Need web hosting? ► hostinger.com/che...
I REMADE My First Game 12 YEARS LATER!
Просмотров 53 тыс.Месяц назад
Try JetBrains IDEs NOW! ► jb.gg/Try-JetBrains-IDEs PLAY THE GAME! ► studiocherno.itch.io/Genesis Patreon ► patreon.com/thecherno Instagram ► thecherno Twitter ► thecherno Discord ► discord.gg/thecherno 💾 CODE Original (Java) ► github.com/TheCherno/Genesis Remake (C ) ► github.com/TheCherno/Genesis-Remake Hazel ► hazelengine.com 🕹️ Play our latest game FREE (made in Haz...
BINARY vs TEXT File Serialization
Просмотров 47 тыс.Месяц назад
To try everything Brilliant has to offer-free-for a full 30 days, visit brilliant.org/TheCherno . You’ll also get 20% off an annual premium subscription. Hazel ► get.hazelengine.com Patreon ► patreon.com/thecherno Instagram ► thecherno Twitter ► thecherno Discord ► discord.gg/thecherno HxD hex editor ► mh-nexus.de/en/hxd/ Hazel ► hazelengine.com 🕹️ Play Dichotomy for F...
I Rewrote This Entire Main File // Code Review
Просмотров 130 тыс.2 месяца назад
Patreon ► patreon.com/thecherno Instagram ► thecherno Twitter ► thecherno Discord ► discord.gg/thecherno Hazel ► hazelengine.com 🕹️ Play the latest Hazel game FREE ► studiocherno.itch.io/portal-me-away Code ► github.com/ural89/ConsoleCraftEngine Send an email to chernoreview@gmail.com with your source code, a brief explanation, and what you need help with/want me to re...
TERMINAL GAME ENGINE! // Code Review
Просмотров 63 тыс.2 месяца назад
Try Code Rabbit for FREE now ► coderabbit.ai Patreon ► patreon.com/thecherno Instagram ► thecherno Twitter ► thecherno Discord ► discord.gg/thecherno Hazel ► hazelengine.com 🕹️ Play the latest Hazel game FREE ► studiocherno.itch.io/portal-me-away Code ► github.com/ural89/ConsoleCraftEngine 🌏 Need web hosting? ► hostinger.com/cherno Send an email to chernoreview@gmail.c...
Conversion Operators in C++
Просмотров 35 тыс.2 месяца назад
To try everything Brilliant has to offer-free-for a full 30 days, visit brilliant.org/TheCherno . You’ll also get 20% off an annual premium subscription. Hazel ► get.hazelengine.com Patreon ► patreon.com/thecherno Instagram ► thecherno Twitter ► thecherno Discord ► discord.gg/thecherno 📚 CHAPTERS 0:00 - What are Conversion Operators in C 9:40 - Real world BUG 13:54 - R...
How New Game Engine Features are Implemented
Просмотров 25 тыс.2 месяца назад
Schedule a Grammarly demo for your team using my link ► grammarly.com/cherno02 Patreon ► patreon.com/thecherno Instagram ► thecherno Twitter ► thecherno Discord ► discord.gg/thecherno Hazel ► hazelengine.com 🕹️ Play our latest game FREE (made in Hazel!) ► studiocherno.itch.io/dichotomy 🌏 Need web hosting? ► hostinger.com/cherno 📚 CHAPTERS 0:00 - Implementing a new feat...
Asset Packs
Просмотров 20 тыс.2 месяца назад
To try everything Brilliant has to offer-free-for a full 30 days, visit brilliant.org/TheCherno . You’ll also get 20% off an annual premium subscription. Hazel ► get.hazelengine.com Patreon ► patreon.com/thecherno Instagram ► thecherno Twitter ► thecherno Discord ► discord.gg/thecherno Hazel ► hazelengine.com 🕹️ Play Dichotomy for FREE (made in Hazel!) ► studiocherno.i...
I ACCIDENTALLY Created Hazel's Greatest Feature
Просмотров 38 тыс.3 месяца назад
I ACCIDENTALLY Created Hazel's Greatest Feature
EVERYTHING takes longer than it seems
Просмотров 32 тыс.3 месяца назад
EVERYTHING takes longer than it seems
40 Days.
Просмотров 38 тыс.3 месяца назад
40 Days.
Path Tracer Code Walkthrough (C++/OpenGL) // Code Review
Просмотров 20 тыс.3 месяца назад
Path Tracer Code Walkthrough (C /OpenGL) // Code Review
From Editor to Runtime - The Hazel Engine Workflow
Просмотров 29 тыс.4 месяца назад
From Editor to Runtime - The Hazel Engine Workflow
This Animation System IS AMAZING
Просмотров 54 тыс.4 месяца назад
This Animation System IS AMAZING
BEST WAY to understand graphics and rendering code
Просмотров 35 тыс.4 месяца назад
BEST WAY to understand graphics and rendering code
I'm Struggling.
Просмотров 103 тыс.5 месяцев назад
I'm Struggling.
I HAD to fix this immediately // Code Review
Просмотров 29 тыс.5 месяцев назад
I HAD to fix this immediately // Code Review
PATH TRACER made by 15-YEAR-OLD in C++ OpenGL! // Code Review
Просмотров 69 тыс.6 месяцев назад
PATH TRACER made by 15-YEAR-OLD in C OpenGL! // Code Review
Learning Programming by Trying and Failing // Code Review
Просмотров 37 тыс.6 месяцев назад
Learning Programming by Trying and Failing // Code Review
My Favourite Way To Make Websites
Просмотров 59 тыс.7 месяцев назад
My Favourite Way To Make Websites
How to Properly Setup C++ Projects
Просмотров 95 тыс.7 месяцев назад
How to Properly Setup C Projects
Don't Make This Mistake! // Code Review
Просмотров 50 тыс.7 месяцев назад
Don't Make This Mistake! // Code Review
Hazel Engine ON LINUX!
Просмотров 45 тыс.7 месяцев назад
Hazel Engine ON LINUX!
Game Engine Architecture 101 // Code Review
Просмотров 51 тыс.7 месяцев назад
Game Engine Architecture 101 // Code Review
2000 HOUR 2D Game Engine! // Code Review
Просмотров 79 тыс.8 месяцев назад
2000 HOUR 2D Game Engine! // Code Review
GPU Particle System, Real-time Global Illumination and More: The Future of Hazel - My Game Engine
Просмотров 31 тыс.8 месяцев назад
GPU Particle System, Real-time Global Illumination and More: The Future of Hazel - My Game Engine

Комментарии

  • @brunomarques5258
    @brunomarques5258 16 часов назад

    Hi the cherno, thanks for this content of graphics programming, a little inspired by you, I've started to learn Vulkan and D3D12, and I found the VMA brother the D3D12MA, and now I've pulled request 2 features, all in cmake. I've followed the cmake path, instead of premake that your prefer, but i really thank you to be introduced in this content.

  • @nexovec
    @nexovec 19 часов назад

    Just today a compiler crashed on me after coming around to compile my hobby project after a year. I have something of this level every 2 months. 😅This is nothing

  • @Raattis
    @Raattis 21 час назад

    With memory mapped files you wouldn't need different back-ends for memory and files. It also happens to be faster than file streams. Just something to consider.

  • @DavidOnonokpono
    @DavidOnonokpono 22 часа назад

    Thanks

  • @AnythingSoupVODS
    @AnythingSoupVODS День назад

    running on 165 hz, 6 ms per frame

  • @Edelina_Heine64
    @Edelina_Heine64 День назад

    I love your videos and want you to keep doing them💛💛

  • @Pablo360able
    @Pablo360able День назад

    Fascinating. I vaguely remember some of this from when I was first learning C++. I think that C# multidimensional arrays are really single-dimensional arrays under the hood, so they avoid some of the downfalls of actual meta-arrays listed here.

  • @MattSitton
    @MattSitton День назад

    Have you considered a merged writer reader serialization interface? Since it's possible to use the same interface to do both using pointers. This reduces the amount of serialization code and massively reduces the error of needing to write the code twice!

  • @xSferQx
    @xSferQx День назад

    I wish everything was manual and simple. All the magic behind the scene is not worth the time you invest learning what is happening there

  • @ohwow2074
    @ohwow2074 День назад

    Endianness, padding bytes, different sizes of data types in different platforms, etc will cause you quite a bit of headaches soon. That's why I forgot about binary serialization and just went with text sterilization. Not super efficient but easy to deal with.

  • @slygamer01
    @slygamer01 День назад

    I do a very similar thing, even in C#. The only real addition is that I version every non-trivial section, i.e. every implementation of Serialize/Deserialize in a class. On read, if the version of that section is less than the current version, it calls the method to read that version. It only ever writes the latest version, but it can read every previous version in addition to the current version. This way it can always load old data, and updates it when the data is saved again. Deserialize() { byte version = ReadByte(); switch (version) { case 1: DeserializeV1(); break; case 2: DeserializeV2(); break; } } DeserializeV1() { // Reads V1 data into the current data structure, adapting/updating where necessary or filling new fields with defaults } DeserializeV2() { // Reads V2 data into the current data structure }

  • @felix8103
    @felix8103 День назад

    std::format in C++ 20 🔥

  • @faizydeveloper
    @faizydeveloper День назад

    Best series i ever seen, #Thanks_Cherno

  • @pannagasudarshan6639
    @pannagasudarshan6639 День назад

    what's memory leak??

  • @pannagasudarshan6639
    @pannagasudarshan6639 День назад

    Lot of questions left unanswered I felt. Why was Printable turned into a pointer in the function argument? lot of scrolling which left me confusing ://

  • @pannagasudarshan6639
    @pannagasudarshan6639 День назад

    Yeah this whole video just went over my head :/

  • @swapansaha2368
    @swapansaha2368 День назад

    Vulkan series pls

  • @swiftcodey
    @swiftcodey День назад

    As a related topic, I'd love if you could cover versioning with respect to serialization. Seems not as trivial as I'd hope.

  • @ultimatesoup
    @ultimatesoup День назад

    That's not always true. Sometimes you have to introduce complexity for flexibility and extensibility purposes

    • @Pablo360able
      @Pablo360able День назад

      in which case the best solution is the simplest one that meets the design goals of flexibility and extensibility

  • @MrSofazocker
    @MrSofazocker День назад

    15:18 "Non trivial types" Why not deconstruct them and save a mapping + the data?

    • @MrSofazocker
      @MrSofazocker День назад

      Ah nvm you are essentially doing that.

  • @MrSofazocker
    @MrSofazocker День назад

    IMO Complexity Demon enters code-base when requirements are not clear and one doesn't see the common denominators. When abstracting things down to the common and shared denominators a system becomes most simplistic in a way, that is so generalized it will fit any use-case. Yes, things can be really simple if built for one thing, and one thing only. But they can also be simple, if not simpler when they were built to the underlying mechanics that govern whatever system you are building.

  • @MrFluteboy1980
    @MrFluteboy1980 День назад

    I'm not great at C#, but why couldn't they just have used a local variable in the Main function instead of a static member object? I suspect it's not happy with the Window being not null because the class in which the Main function sits doesn't instantiate the Window object in its constructor, but that would mean having to still instantiate a brand new unrelated object, and have a constructor for it, just to remove the question mark

  • @SirusStarTV
    @SirusStarTV День назад

    What a fun and easy process

  • @nitrogenez
    @nitrogenez День назад

    i did some serialization in zig. let me just say that it's much less hassle for the task than in c++.

  • @SeishukuS12
    @SeishukuS12 День назад

    I wouldn't write a size_t to a stream, it *could* be different on different compilers/platforms/OSes, should *always* use a known size type when doing data streams. It's been mentioned in other comments, but endianness can also come into play, but also a majority of systems out there are little endian... So that's less of an issue IMO.

  • @marcususa
    @marcususa День назад

    Way too much babbling. I lasted 10 minutes, then stopped.

  • @shadow_blader192
    @shadow_blader192 День назад

    MGE 🤔

  • @EraYaN
    @EraYaN День назад

    Any reason for not using any of the serialization formats that use a code generator? Protobuf, cap’n’proto, msgpack etc.

  • @mlecz
    @mlecz День назад

    While this code effectively reads and writes int32_t and int64_t, it's important to note that it may not be cross-platform due to differences in endianness between systems. Little-endian and big-endian architectures store bytes in different orders, leading to potential incompatibility when sharing binary files across platforms. To ensure cross-platform compatibility, consider explicitly managing byte order or using libraries like Boost which have functions like native_to_big and big_to_native to handle endianness conversion.

    • @NullPointerDereference
      @NullPointerDereference День назад

      ChatGPT wrote this comment.

    • @mlecz
      @mlecz День назад

      @@NullPointerDereference yup I'm too lazy

    • @MrSofazocker
      @MrSofazocker День назад

      You generally can tell the system which one to use, its a non-problem.

    • @mlecz
      @mlecz День назад

      ​@@MrSofazocker How in C++17/23 without using assambler can I do this globally? endian.h from c++ provide only functions like htole32 and htobe32 similar to native_to_big and big_to_native from boost? Some chips provide method to switch but not all.

    • @Pablo360able
      @Pablo360able День назад

      @@mlecztoo lazy to write your own comment on a youtube video? what's this world coming to

  • @crouette
    @crouette День назад

    The "difficult" part is not structs/objects... it's shared struct/objects, that are saved in one place and then, pointers to them need to be recreated when loading... pointers can act as a "uid"... but that means that a struct/objects when loading need to have a place to store the pointer at save time, and when loading look for it in the loaded objects. That is introducing some dependencies, and so, the order to save and load becomes important.

    • @MrSofazocker
      @MrSofazocker День назад

      Why do you think that? The datatypes already are loaded into a tree, which you can derive their usage through, data gets stored somewhere every use gets the pointer to it? Done.

    • @Muzzleflash1990
      @Muzzleflash1990 День назад

      @@MrSofazocker In that scenario they might not be in a tree; the pointed structs might form a graph. Then you cannot simply use WriteObject as done here since you might with mutual recursion. While you could serialize the pointer as an ID, you still need to also associate the objects with the ID in the serialization itself. And when reading, the pointed to object might not have been loaded yet, so either you would have return to the object later to fix the pointer, or you can load it immediately assuming you have associated the current object with its ID. None of this is insurmountable at all, but still more difficult. Even in the simpler case with just a shared object, you will still need to avoid duplicating it when deserializing again.

  • @paradox8425
    @paradox8425 День назад

    Great content, but where is Vulkan series? :d

  • @dino_source
    @dino_source День назад

    Why to use char* data to represent bytes in 2024? Is the std::vector<std::byte> not good enough? We have std::byte since C++17 if I'm not mistaken...

  • @kingofspades9720
    @kingofspades9720 День назад

    Someone already commented this, but if you get something like this #error OpenGL already defined The reason is your includes in ImGuiLayer.cpp has to be like this #include "voxpch.h" #include "ImGuiLayer.h" #include "imgui.h" #include "backends/imgui_impl_glfw.h" #include "backends/imgui_impl_opengl3.h" #include "Voxen/Application.h" #include <GLFW/glfw3.h> #include <glad/glad.h> If it looks like it is the exact same as what you have, it isnt, you are likely including 'imgui_impl_glfw.cpp' and 'imgui_impl_opengl3.cpp' but those both need to be the .h in the ImGuiLayer.cpp

  • @mlecz
    @mlecz День назад

    Cherno, you don't need to call the .close() method on std::ofstream in destructor because RAII (Resource Acquisition Is Initialization) automatically handles closing the file when the object goes out of scope. Manually calling .close() leads to redundancy since the destructor of std::ofstream closes the file automatically.

    • @MrSofazocker
      @MrSofazocker День назад

      Nope, while redundant it's more implicit writing. It's not gonna close twice or smth, or idk what you think.

    • @mlecz
      @mlecz День назад

      @@MrSofazocker this is redundant in terms of style and code management, as well as good practices for complying with the principles of the RAII idiom

    • @ohwow2074
      @ohwow2074 День назад

      Close can fail inside the destructor and the destructor will hide the error. By calling close before the destructor is called you ensure that nothing inside the destructor is going to fail without you knowing about it. It's a clever technique.

    • @mlecz
      @mlecz День назад

      @@ohwow2074 but the problem is that there is no exception handling in his code which would make your theory valid in this case

    • @ohwow2074
      @ohwow2074 День назад

      @@mlecz he said he didn't have time to implement error handling. He'll probably do it in the near future. Also no need for exceptions. Calling close and then checking the state of the stream with `if (!stream) return error_code;` is the way to go.

  • @kingofspades9720
    @kingofspades9720 День назад

    "I'm not even sure how that code I wrote yesterday worked at all" - Every programmer 23:39

  • @nan0s500
    @nan0s500 День назад

    What about endianess of architectures? Am I correct that you don't handle it or am I missing something?

  • @Arwahanoth
    @Arwahanoth День назад

    why not use std::ostream and std:istream interface?

  • @ferdynandkiepski5026
    @ferdynandkiepski5026 День назад

    Yeah, as others have said, add versioning. The first few bits should be used for checking the version.

  • @anon_y_mousse
    @anon_y_mousse День назад

    I still think you should just use an open source library for some configuration format like TOML and just serialize all data that doesn't fit into a basic type as a b64 encoded string, but that may just be me who is thinking that and you'll never read this anyway. Also, you must have written Java for many years looking at how you're designing this.

    • @Muzzleflash1990
      @Muzzleflash1990 День назад

      Why on earth would expand increase the size by 33% and do extra processing for b64 encoded strings unless you are passing it from and to poorly made web services that have escaping errors?

    • @anon_y_mousse
      @anon_y_mousse День назад

      @@Muzzleflash1990 Since my responses keep getting deleted I'll say it again, to prevent data corruption from users that edit the file with notepad and for web services.

    • @Muzzleflash1990
      @Muzzleflash1990 День назад

      @@anon_y_mousse It a simple transform (assuming you already have it byte-serialized) to convert to b64 if you need to. But there is no need to pay the cost up front if you never need to. I see what you mean now with TOML and b64 serialization for the "too complex" types. Having user editable data format though has entirely different considerations and goals than a binary encoding for performance. NVME SSDs are faster nowadays than many JSON parsing libraries.

    • @Muzzleflash1990
      @Muzzleflash1990 День назад

      @@anon_y_mousse Also, 9 out of 10 times I include a link in a YT comment the entire comments gets auto-deleted. YT really is a terrible platform for technical discussions.

    • @anon_y_mousse
      @anon_y_mousse День назад

      @@Muzzleflash1990 I referred to the program by its filename and that's probably what triggered it. Possibly another reason to dislike D's scope resolution operator over C++'s.

  • @xthebumpx
    @xthebumpx День назад

    How much simpler would this be with something like Serde in Rust? Is there no good C++ equivalent, or is something like that generally not suitable for a game engine?

  • @RelayComputer
    @RelayComputer День назад

    It doesn't look much different conceptually than Apple's Foundation NSCoding protocol. Serialising and deserialising is totally manual but extremely simple too.

  • @miket591
    @miket591 День назад

    I would really recommend you to dump std::ofstream, wrap a low level FILE (fopen) API, as it can easily be between 2x to 10x faster to read/write files than std::ofstream. Please test it.

    • @ohwow2074
      @ohwow2074 День назад

      Just switching to a C API makes things 10X faster?

    • @miket591
      @miket591 День назад

      @@ohwow2074 It has nothing to do with it being C but how they have been implemented. std::ofstream is build upon FILE but adds an extra layer of buffering and lock access, in addition to stream checking and accounting. If you use FILE directly, you only get 1 buffer layer instead of 2, and you can "not use" the associated lock given that mostly likely you will never have 2 threads competing to read the same file stream simultaneously. I have measured this.

    • @ohwow2074
      @ohwow2074 День назад

      @@miket591 interesting. I'll try and dig deeper into this.

  • @K3rhos
    @K3rhos День назад

    4 things that got to my eyes instantly: - Like you mentioned the useless if - The usage of what I call an "OG loop" ahah, honestly a foreach would be cleaner here, since the index "i" doesn't seem to be involved in any other things rather than just getting the element from the array. - The usage of 2 "GetComponent" that can be associated to a variable like this: Collectable coll = g.GetComponent... - And also a check to see if the component "Collectable" actually exist on the gameobject.

  • @lennymclennington
    @lennymclennington День назад

    I don't understand why you need to explicitly define a destructor to call m_Stream.close() when the destructor of m_Stream itself will close the stream. Standard streams already use RAII. It would also probably make more sense to construct m_Stream directly in the constructor's member initialiser list rather than constructing a temporary and move assigning it in the body.

  • @nextlifeonearth
    @nextlifeonearth День назад

    To me the static member function is polluting the data type. You can just make functions, so you may even put the serializers in another file and don't have to include your serializers if you don't need them.

    • @Muzzleflash1990
      @Muzzleflash1990 День назад

      That will highly couple the now separate serialization implementation to the data type, and you would not be able to serialize private or protected members.

  • @ScottHess
    @ScottHess День назад

    I once implemented a serialization framework where I abstracted write and read to a certain extent. Instead of readInt() and writeInt(), there was processInt(), with a pointer to an int (and so on). The reader object would fill in the pointer, the writer object would write out the pointed-to value. Maybe 5% of code needed to test whether it was reading or writing, for instance if it needed to initialize other structures on read, but you didn't want to run that code on write. Doing it this way really removed a lot of the boilerplate associated with such systems. [In general you have my full agreement on simple, though. In my experience self-deriving serialization libraries are only useful for tech demos, for real code it is worthwhile to do it right the first time.]

  • @AadiPoddarKolkata
    @AadiPoddarKolkata День назад

    what's the visual studio color theme

  • @elirannissani914
    @elirannissani914 День назад

    So the first 10 minutes is you crying on your little project

  • @julkiewicz
    @julkiewicz День назад

    The simplest solution is the best solution - and that's why I chose C++ :) Just kidding, I know there isn't much of an alternative really

  • @utilyre
    @utilyre День назад

    This pattern is gonna be so useful for my final project. Thanks man