- 6
- 90 147
cient
Приєднався 4 лют 2024
game development time
Adding a Player to my ECS game engine
My ECS library: github.com/chrischristakis/seecs
Music:
Secunda - Jeremy Soule (Skyrim)
Beacon - Sonny Boy
By Your Side - Pedro Silva (Omori)
The Herald - Darren Korb (Pyre)
Gameplay footage:
Skyrim
0:00 Intro
0:36 Movement
2:26 Dash
4:12 Cooldowns
Music:
Secunda - Jeremy Soule (Skyrim)
Beacon - Sonny Boy
By Your Side - Pedro Silva (Omori)
The Herald - Darren Korb (Pyre)
Gameplay footage:
Skyrim
0:00 Intro
0:36 Movement
2:26 Dash
4:12 Cooldowns
Переглядів: 6 990
Відео
Optimizing My ECS Game Engine to Simulate 100,000 Entities | Sparse sets
Переглядів 15 тис.3 місяці тому
seecs: github.com/chrischristakis/seecs Big thanks to skypjack for his comprehensive articles that I referenced heavily for the topics covered in this video, you can find them here: skypjack.github.io/ Music: - Halls of Science 4 - Portal 2 OST
Recreating Winston's shield in OpenGL/C++ | Intersection Shaders
Переглядів 42 тис.5 місяців тому
A little different to my usual stuff, but I promise the next dev log is coming sooner rather than later. Just wanted to take a detour to explore some cool graphics stuff like the intersection shader Music: Rhombus - By Peppsen Github Repo: github.com/chrischristakis/Winston-Shield CHAPTERS: 00:00 - Intro 01:15 - The plan 02:00 - Depth texture 04:43 - Linear depth 07:45 - Smooth edges 08:10 - Ri...
Adding sprites and animations to my game engine
Переглядів 3,6 тис.6 місяців тому
Data locality segment inspired by "Game programming patterns" By Robert Nystrom Music: - Rhombus by Peppsen
Making an Entity Component System for my game engine
Переглядів 20 тис.6 місяців тому
Next one will be SENSATIONAL! Design is inspired by 'A Simple Entity Component System' by Austin Molan Music: - Circle (Menu) by Peppsen
Making my first game after 7 years of game dev, with no engine.
Переглядів 2,3 тис.7 місяців тому
we love love LOVE engine development. Music: - Circle (menu) by Peppsen
Can the outline be acheived using raycasting instead? is this option more performant?
I compiled your project to emscripten and got it working! I appreciate this projects existence because it was a base for me.
Great video! Love graphics stuff.
From all the videos i've watched about ECS yours is most concise and clear. Great job on the explaining and visuals
Aghh thank you :)
some comments on the part about how the system can efficiently know which entities has each component... What I do is that each system has their own entity array so instead of looking into the global entity list each just process their own list avoiding having to have each system checking the pool each frame. the downside of this is that an entity initialization cost is higher since I have to check each system to know which one to slot but can be negligible since you may not have thousands of systems to check against
Honestly, as the game scales, that might be an approach I'll have to consider. I still need to iterate each group so as the amount of systems grows, the current implementation becomes more and more of a bottleneck. I think needing to iterate each system once on entity creation sounds better than having to iterate each group for every system.
@@cient_dev ofc. we need to keep in mind that any component removal / addition to the entity would also trigger an interactions to the system so also you would need to keep in mind the house keeping of where the entities are
Awesome work bro! The YT algorithm just recommended your video, and it's great to see a more low-level approach to game development. Maybe one day, I'll adventure myself on a journey like that. Please keep up with the videos!
Anyone know what font was shown in the video? Thanks!
Cascadia Code :)
very cool shit, my man. subbed
This inspires me to pick up my ECS-based game project again! Very nice explanation of the practical execution of a ECS based game.
Very nice. I would just suggest not using std::unordered_map inside your components, because that class uses heap memory which will ruin cache coherency in your systems. Since Actions is an enum, just make a special value Actions::COUNT as the last value, and make Cooldown::data a fixed-size array with size equal to Actions::COUNT.
Good catch!!
luv a fellow ECS enjoyer/author :D
Another awesome video, thanks and keep it up! Always look forward to these.
An outstanding video as always! I have a question, in your ecs library i'm seeing a lot of `.ForEach<...>` and stuff inside can you show in way how did you accomplish that? and I don't just mean code side, since there has to be some decisions made performance wise Cheers :D
Thank you! From a code perspective: It's all available in the description! But for performance, every time I add/remove a component, the entity is sorted into a sparse set that groups entities with similar components, and the for each just iterates and returns each of the entity's components in the relevant groups as a parameter in the ForEach function (Went more indepth about some decisions in the last video). It's flexible but definitely still a lot of room for improvement though!
that's very interesting, thank you for the insights!
Cool stuff. I've also started exclusivly using ECS since I made my own. Creates really good decoupling.
Wow, that is not something I actually noticed until you pointed it out, very cool! Thanks for sharing/explaining.
respect to my brethren in the low-level / gamedev sphere
The godpointer😂😂
Assuming DT is deltaTime, don't you want to use fixedDeltaTime?
This is in my own engine which uses a fixed timestep on physics; so I pass in the fixed timestep as deltatime. Maybe it would be better for me to rename it to fixedDeltaTime for clarity
I had heard it was your own engine, but since most engines implement standard DT and fixed DT, a lot of bugs can be generated by using the incorrect one If there is a possibility of you wanting a non-fixed dt in the future, future you may appreciate the clarification 👍
This is what I want my cpp code to look like heh. On the side, what theme are you using? It looks awesome
It's called Github Dark, part of the Github themes extension on Visual Studio :)
Let's gooo! An amazing explanation of ECS. I wish it had been explained to me in such simple words a while ago though! Thank you!
That is some really awesome pixel art!
Thank you! :.)
Another great video! Looking forward to what’s to come
Next video is all about suffering!
@@cient_dev what’s a good coding session without sweat and tears am I right!
Sorry for the delay! I just wanted to make this video quickly before I put my head down and add more things to the game. I'm still not ready to share the exact premise of the game (I promise it's not a top down adventure game) but *hopefully* I'll be able to release a prototype for anyone who wants to check it out within the next handful of videos. Thanks again for sticking around :)
0:31 WHAT WORDPRESS IS A GAME ENGINE?!?!!?! LOL
@@99u99 Anything is a game engine if you're brave enough
u have the portal music :D
I need to know what VS Theme you are using :/ (plz)
10 years development exp here, 6 prototypes - mmo type - still not final game but i atleast i proved to myself that you can create mmo single person you just need to learn a lot :D
Great video! I'm wondering if you can improve the rendering by just rendering the geometry as normal, and then just grabbing the depth image out of the framebuffer (maybe with hijacking the linear depth as you've shown). Probably possible to do in Vulkan, where you can have 2 render passes (or subpasses), where the second pass has disabled depth writing (you don't need it) and just feeds of the depth texture of the first pass (if you wanted depth write, you could just copy the depth texture in between or use a different one).
I did something similar in my implementation. I used std:bitset to handle the entity component mask, when I add/delete components, every active system will check the component mask, and if it satisfies the required mask for that system, it will add it to its own internal list of entities that it will iterate over. All of my component types also automatically have an associated bitset that is used when generating the bit masks for each entity and system.
as a self thought c# programmer, graphical programming makes me cry lika a little baby and just crushes my soul and self confidence, big respect to graphical engineers o7
. How would you get a job in the future , from self thought
You publish projects and get the attention of recruiters @@ontheotherhand463
Very very very difficult!!!
Hiya, I'm a firmware engineer by trade, and divisions and modulus give me the heebiejeebies (the slowest arithmetic operations). Consider using power of 2 page sizes. If you have a pagesize of 2^L, then page_index = index >> L; page_offset = index - (page_index << L). If pagesize is 256 then L=8 etc
I just did this for my version of a sparse set ecs in odin. Works great, saves minimal time when working with 10,000,000 entities but still, it does improve the performance.
@IogaMaster That's good to hear. I'm used to minimal hardware which often don't even have floating point support. I think modern processors don't mind division and modulus too much, compared to some of the lower end microcontrollers. If the bulk of the work is not in indexing data, then access times will likely not improve performance more than optimizing the bulk work. For simpler tasks, I'd imagine you may gain a significant speedup.
Thanks, I've always wondered how to do this!
2:35 Wow, why didn't I think of this when writing my dynamic list implementation? I've been doing the O(n) memmove() removal they always talk about in school.
Why instead of .pop_back on the dense list instead set the length of the list to len - 2? And also why use swap when you only need to overwrite the dense[index] with dense.back()?
I would ask why you didn't just use EnTT as that's the one that a lot of modern games (AAA and indie) use. But you worked off his articles on ECS design, so I guess it's generally good practice to have an idea of the underlying code. Impressive optimisation 👌.
This reminds me of a hash table implementation I've done, except your hash index is a procedurally generated ID instead of a hash of the data. Which makes me wonder which hash map implementation you were using that it tested so much slower. If it's in the standard library then that makes sense since most implementations are poorly written, but especially most STL hash maps. My own experiments in implementing hash tables show me that there are many tricks to be used for speeding everything up, chief amongst them is using a power of two table size so that normalizing the hash code is a single op, simplifying the hasher because it doesn't need to be perfect, just good enough for right now, and one trick that you show here whereby you swap the element to delete with the last element and merely pop. Moving an entire array down one element because you constantly delete the 0th element is a massive waste of time. Also, what's the largest entity that you create and are you overlapping the types? If you're overlapping the types, are you using inheritance or a union?
Beautiful 😢 great job 👏
Great video and explanations!!
Not to be rude, but 100.000 entities is not that much actually, this can be done without ecs and could still be fast
Why componets? Why not nodes? Would it not be simpler to cache optimize not having to handle components with entities and just use single datas so nodes?
what do you mean exactly?
@@lavatasche2806 well. It would be simpler code not to have to handle the concept of an entity. So I figured it might translate into faster code in general too? Chaching is about the right order but also about memory size. And preparing memory also gets slower the more extra you have to do. I have not tested yet anything myself though. I was just wondering. I am just about to finish the plugin system's handling of "editor" plugins so that the PNS kernel does not load editor nodes/systems if it is in runtime mode. And that of course the kernel tracks what even belongs to the plugin in the first place. Bevy can not do auto registering of types fully either. My kernel can because it does not use type unsafe handles like entity handles. Bevy seem not to have component handles either. So disclaimer is that I am in general anti entity/components and use nodes instead. A node handle is obviously type save. That allows me to scan an entire hierarchy of such nodes. Whether that extra info could translate into more performance ontop of the full auto type registering that I do not known yet. In the end of course it is the systems that require the access so determine the access pattern. Mind though that regarding optimized access Bevy and my kernel may run faster because we got a systems executor. Some ECS do not. Any system in the same "algorithmic" order can be executed within in that algorithmic order in a different system order depending on what data it accessed. My point is just that for the record that ECS kernels without a system executor are not a thing. It is not only about how you store the data but how you access it. Bevy seems to allow this pure fetching nonsense too. No to mention that the entire idea of flexibility of an ECS (or in my case PNS so Prefabs Nodes Systems) is also compromised in non system executing kernels because systems are also entry points for plugins. Bevy though does not force plugin use either. Bevy also for some silly reason requires like 5 extra crates just to use the ECS kernel without the Bevy engine. That is why I started my own PNS kernel. The entity/components part was not even the first problem. Godot's engine also uses a nodes based kernel. Though it seems it uses ECS too when handling mass processing. Problem with ECS is that it is making thinks more flat then they actually are by allowing type unsafe handles like entity handles. Instead of spaghetti code you get lasange code. Bottom line is that my PNS vs ECS war may end in a draw. I was initially afraid that ECS may be much faster then PNS. But the goal of my PNS kernel never was to become a speed demon but be ergonomic. That is why unlike Bevy it only needs one crate instead of 5. And forces systems and plugins. And also has a runtime/edittime mode for plugins plus compile time definition of configs/assets/scene/savegame serialization instead of a runtime like in Bevy and likely any other kernel. Seems I can also remove the "query" type bloat from the system signature. I still do not understand why that is in Bevy in the first place. It flies into the face of ECS kernels that have a system executor and do have the querying under the hood. Anyways... sorry for the text wall. :/
@techpriest4787 yes the rambling is unecessary. I read your whole wall of text and still have no idea what you are talking about. What exactly do you mean by nodes? Are you talking about nodes similiarly to godot? What is the difference between nodes and components other then that nodes are in a tree structure. You still need the concept of an entity in a node based system? Nodes are very much not known for their efficiency and that is why I am confused
@@lavatasche2806 nodes are essentially components just without the entities. I guess it is the misconception that they are locked into a hierarchy. But they are not. ECS is just doing a different kind of access. That is why it is able to break out of the hierarchy and do things in parallel and also have an awareness in parallel what the types are so be able to control execution/ access order so do cache optimization. By using a fetch or query so find them not via handles but by type and that from a centralized storage. My PNS kernel does the same with nodes. One also can not break out of hierarchy to 100% in an ECS either. There is obviously a limit to how parallel a code can be. And that is all ECS really does frankly. That is why even an ECS does use handles. Bevy uses entity handles that are not type save because entities are not types. So ECS also requires hierarchy like access but in an inferior way. Perhaps not from a performance point of view compared to PNS nor do I think at this point that it has an advantage either. But from a clean code point of view obviously it is lacking. Leading to other difficulties of not being able to auto register data at startup because it lacks hierarchy information because of lack of type information from the hierarchy side in the first place. The handles obviously reintroduce the concept of a hierarchy again. If I am not mistaken then unlike Bevy the Flecs kernel actually provides handles for components!? Either way. Maybe I am just too dense because I am still sort of new to this. But I just fail to see the point of entities. I only understand the merit of "systems".
@@techpriest4787 entities are needed to associate the components with something. How would you differentiate two enemies with different components for example?
It’s amazing how many things you can do with a depth buffer. This is why I love graphics. Why do you use OpenGL tho? Isn’t it a dead API?
It's what I know and does what I want; I don't really have a reason to justify the time it takes to learn Vulkan or DX :P
@@cient_dev well, we’re in the same boat then :) One thing that might tips me over is that OpenGL error message is so bad, it never tells you wth went wrong beside an enum and sometimes the doc page doesn’t even include the actual reason why it failed, D3D is better at that.
Really cool video ! I've never really used ECS a lot, but you made me want to try it, Bevy seems to have a really good ECS, have you already looked at it a bit ? I need to look at how they are able to pick which entities/components to use in a system only using types.
Thanks for a cool video
I wanted to ask where do you learn all this, are there any resources you especially recommend? Also congrats on new job :)
Thank you!! This stuff in particular was just the result of looking up multiple articles about ECS and stumbling across the term 'sparse set' multiple times. From a more general sense, I started by learning C++ and getting comfortable with it, then I learned OpenGL for graphics/rendering, then after that, it's just a matter of practise/delving into any topics you come across that seem interesting. Here's some notable resources I used myself to learn: C++: www.learncpp.com/ + TheCherno on UA-cam OpenGL: learnopengl.com/ + Lots and lots of extra learning via videos/articles Game dev: Game programming patterns by Robert Nystrom Only go into this stuff if you really really want to learn it, I'd highly recommend saving the time and learning an existing engine; I just find the engine development stuff more fun. Good luck!
Your videos are really enjoyable and informative. You remind me a lot of miziziziz but your editing is just way better. Hope to see more from this channel!
His voice is very similar, in a good way
Great music & even greater video. Love it <3
My favourite song on the portal OST :.)
well if you are running through the entities at equal timing, then you can just use a double-linked list and move things around to pack. this way you can have a worker thread navigate the linked-list for individual updates separate from the game loop, in fact, the sorting algorithm can also run in its own worker thread. majority of things would be read-only, but then you can block pages of memory in a semaphore map if you need to write or alter something....you can also semaphore block variables themselves so that if they point to the same memory, they can be altered without damage to the thread, then when unblocked, the thread can continue without realizing it is now looking at a totally different location. This way you don't have to have a sparse set at all and point-through for data access....you already hold the data in situ.
Maybe! It's hard to tell, plus the linked list should be packed in memory to take advantage of the cache. Then again, you can forget the cache and implement what makes sense for you! ECS is still somewhat young and has many avenues, and I don't think what i have here is a top contender for one, but the simplicity + reduced memory overhead is enough for me Let me know your findings though if you explore this route!
question on systems, if I create a system that requires component A and B does that mean, for each entity with the respect bits set in the bitset, I need to "fetch" the components, ie, go thru 2 indirections, get the page, then get the component index for each component, wouldnt this be worse ?
Yep, we need to first get the bit position via map, then we use that bit position as an index into a vector holding the different component list. Maybe you can just make each entity hold a map that contains a key as the component name and a pointer to the component pool as a value so you only have 1 layer of indirection (as I had in my old implementation) but that's expensive (each entity needs a map now instead of just a bitset) and across millions of entities it only gets amplified. Comparing this to my old 1 layer of indirection implementation but more expensive keys/map, I found this to be a lot faster with benchmarks
for pagination, you can use std::array, which I believe doesnt cause any allocation like std::vector does (page size is a constant)
also combine pagination and map ? but rather than using std::map for saving on page allocations, u can write a custom simple map basically create a vector of index -> index mapping, which maps the required page index to the actual sparse page index, this would avoid the many heap allocations and lookups would be faster than std::map the only downside to this is that the memory usage maybe slightly higher than the map approch
The problem with using array is that it preallocates all of its elements, so if we have two very spaced out pages, everything inbetween would be allocated if we used array, and be a memory issue, despite avoiding allocations/being faster on average
@@cient_dev no no, I meant a vector of arrays rather than a vector of vectors where each array is a page then use a map to only allocate pages that are in use rather than using std map, we create a custom vector based map, this does use a little bit extra storage (array of "virtual page index -> real page index" but if the page size is large (like 1000, that you suggested in the video) this vector map would not be that big so tldr 2 vectors vector<array<uint32_t>> // all the allocated pages vector<uint32_t> // sudo map that maps virutal page index to the real page index in the above vector
if you have a discord server, I can share my impl to better explain what I mean 😅, if not all good, great work tho :D
I love your explanation's!