When implementing multi-threaded game engines, distributing work across threads requires careful synchronization to prevent performance issues. The developer demonstrates how dividing game update tasks (prefabs) into chunks assigned to worker threads can cause lag due to threads fighting for jobs and synchronization overhead. Key challenges include ensuring all threads complete their work before proceeding, managing thread sleep timing to prevent CPU overutilization, and handling entity creation/deletion safely across threads. The solution involves using work queues with read/write indices for synchronization, ensuring all threads finish before the main thread proceeds, and carefully managing sleep durations to maintain consistent frame timing.
Inmersión profunda
Prerrequisito
- No hay datos disponibles.
Próximos pasos
- No hay datos disponibles.
Inmersión profunda
Multiplatform C game engine from scratch | Day 56 [Pooling Work]Añadido:
Good evening everyone.
I don't have any particular plans today I think.
But what I was thinking about working on today was scheduling some of these threads to do some more work for me. So you might remember if we are running the game and we just break somewhere.
I guess not there.
Hello.
Oh, right. If we just break somewhere, we can look at the threads here and you will see that we are having a bunch of threads here and pretty much all of them are just sleeping. There is one that is doing graphics and I'm actually not sure which one right now. I'm I'm a little bit surprised that all of them are just sleeping.
Oh.
Oh, interesting.
Why did that happen?
I just paused for a second.
Okay.
I'm not sure why that [laughter] happens, but uh that's not my focus right now. I wanted to take a look at uh do we not have graphics here.
Oh, not the not the header.
Now I'm really confused.
Where is graphics? OpenGL C draw.
Here we go.
Okay, so this one is actually running.
Am I still sleeping in the game?
I am well that that uh tells me why it was sleeping when I was just uh stopping randomly.
So now we can pause at a random place here and you will see that it's in graphic straw and for some reason it's doing that.
Oh, interesting.
So, yeah, I guess we're having some uh bugs that we can take care of.
Cuz that shouldn't happen.
[laughter] Looks like it's inverted.
It's using the width for the height and vice versa or something like that.
Um, we will take care of that at some point.
But yeah, we are having a bunch of threads that are doing nothing except sleeping right now because they're not scheduled to do anything. This is the game threads.
So I would like to take care of that.
Now all of them should have access to some pool.
We can take a look at that.
Okay. No, I'm just assigning it. So then my idea would be we create another queue here.
And there's a couple of ways of tackling this issue.
What we're doing right now is we're doing game updates and then we go through all the prefabs and then if they exist because we might have holes, right?
If they exist, we call the function and then we do the same thing for their children.
uh we update their previous upset offset and later on we are calculating the physics.
Now this entire thing is probably the bulk unless there's some function like I haven't added any function that would be super heavy right now.
But there's two ways of tackling this.
So let's just go over that quickly.
One way is that we're having this entire array. We can think of it as a block of work that needs to be done.
So we can just look at the count here, this count, and we can say, well, we're having seven worker threads. So we will divide it into a bunch of blocks.
I don't know how many that is. One, two, three, four, five, six, seven, eight. I want because Each one of these can have its own thread assigned to it. 1 2 3 4 5 6 7 and 8. And you're thinking, why are there eight blocks when we're having seven threads? Well, we were having the main thread also. So, let's not have that just idling around. We can actually use it.
the alternative to this and that would be in particular if we're doing these function calls somewhere.
If we're doing these function calls, you know, you you could imagine that one function takes a microscond and then another function takes 100 microsconds.
So it might not actually be u prudent to just divide it straight up.
You might actually want to schedule each prefab to have a thread Q task assigned to it. Now, one issue with that is that you're kind of destroying the um coherency, the the memory cohesion.
So one job you're doing one task and then the next job it might be several uh array indexes down and then you have to throw out the entire cache that you have and uh start over. So that's inherently going to be slower just because of that, but it also minimizes the factor of one function taking way longer than any other. But I don't suspect that we're going to uh come into that issue a lot.
Um, so yeah, I think I I don't really see any negatives with doing this approach.
So I think I'm going to go ahead and do that.
Now, we're not going to get perfect.
Uh, we're not going to be able to divide this perfectly, especially if we only have a couple of objects. You know, let's say you have five prefabs here that you need to update, but you have seven threads. Uh, I guess each prefab just gets its own thread and then the other ones don't.
So we need to think about the logic here.
We have a total count and we would like to divide it [snorts] by the number of threads that we have. Do we have the number of threads saved? I believe so.
Thread count.
So this can be an uh integer divide.
And what do we call this? A prefabs to per thread, right? Because we're including the main thread.
So now this might It might be six, it might be eight, it could even be nine.
Uh, I'm not really sure. And this one could be zero because imagine that we're having less prefabs than threads.
So then we say if prefabs per thread is equal to zero then we schedule each prefab its own thread.
else it could be one.
So actually let's say less than or equal to one or no no no because this is an integer divide.
So this could be like 1.99 and it would be rounded down to nine rounded down to one.
But actually we want two prefabs per per thread.
I think the logic just works even if it's one.
So then we we need to create a job for it to to do.
So we are giving it uh probably we need a new new function for this because this is just kind of part of the game update.
So let's put it in prefab actually.
Prefab create. What we have here?
Destroy.
count maybe. So we would give it a starting index start index and then we give it a number of prefabs to actually update.
Makes sense. Yeah.
See, let's just throw it on the bottom.
And I think this actually should be TFL return for result.
And then we do basically everything that is in here. So we start and we end.
And we can disable this.
So the prefab we get is start index plus I and we do it I times uh count times and then we do all this of why is it equal success means oh because it's an assert I Maybe we just do an if here.
No, let's let's keep the assert because I I want to immediately deal with this and because I have a count I I am not checking for success every time.
Then we go through the children. Okay.
So, we can I guess it's just a void.
Now I am not assigning a I'm not assigning a specific thread a specific job.
I want them to share the work Q.
So I think I can just throw the work in there.
So while or do while jobs left to do is the count and then while jobs left to do is larger than zero.
We do this loop.
And what is the loop? The loop is red task uh how did I how do I do it?
Task push.
Which Q? Well, it's the Q physics Q.
Maybe I will call it worker Q.
The function is the one we just made.
Prefab update.
Start index.
It would be zero.
count.
Wait, these are the parameters for prefab update.
uh the stride is for the arguments. So, so we need to add this.
I don't need a line on everyone. I'm almost certain.
But I guess we'll find out the hard way.
So we call this a prefab update.
Start index is job index and count Is this?
So we push that stride is okay. It is relevant.
incompatible.
I see.
Yep.
Now you could have a two functions. What am I doing?
One that is the original function and one that is the thread function to call.
But I don't think really we're going to need need to do that.
It's a little bit annoying to type it, but I don't think we're going to need to type it very often.
So, that should be fine except we need to res uh return uh success.
So now we should be scheduling something but we need to still process it.
So that's done down here.
And this is part of the bigger thread run.
I might just uh pull it out because I want the main thread to also be able to just pop in there and uh do the same thing essentially.
And possibly we want uh this also, but it's so short. Maybe it's not necessary.
Do we need this OS or anything?
I don't think so.
See a third game.
So, we need to do the same thing as here.
We're getting a task. It's coming from the work queue.
If we found a job, we process that, it's going to be this.
And then I guess we don't need to call anything afterwards.
Maybe we'll just call it process thread Q.
And you might see that actually here we can also do the same thing.
But When we do this Yeah, that seems good.
So now I think the cues are uh the I think the threads are doing the work.
But of course, like I said, we want to use the work uh we want to use the main thread as well.
and job index plus equal prefabs per thread.
So I don't think we need this The question is we could um there's a chance that we are having an uneven amount of work to do.
So for example we have I don't know let's say we are having uh 20 jobs but we are having 21 if we would schedule everything everything would be scheduled uh on uh seven.
So we we schedule we schedule seven jobs and then seven jobs and then seven jobs and the last one overshoots. So instead of having 20 total jobs, we would get schedule 21. So the last job would be out of bounds.
So we can prevent that.
Hold on.
This doesn't work.
We need to do this first.
I think this logic is sound. So we are starting at zero.
Let's say we are adding four and we are having five.
So 0 0 plus four is not larger than count.
So this doesn't happen. So we say zero and four and then we push that and then we add four to it. So we get four. Next time 4 + 4 is eight which is larger. So we change this to be one and then we start at four and we do one job update we get five and then we quit the loop right make sense and then lastly for you.
Maybe this should be part of the reds library.
or Hey, maybe process. Oh, Sure.
So if I only want one task, I can just pop it. If I want to process all of the tasks, then I can run run this one. That that makes sense.
So yeah, we are joining the task force.
And if I did that correctly, I should not see any difference except that everything should run faster.
Now something is going on here.
Make something super low level like Python.
Um, sure.
Maybe not. Now what is going on here?
That is not what I expected.
All right, hold on. I need to grab some more water. So, I'll be back in one just one second.
See, I don't even know if you can see it on the stream. Let me take a look.
You You can't see what I'm seeing. Uh I am seeing much more uh psychedelic than uh than what what you do.
But that is surprising to me.
So, let's maybe take a look what at what's going on.
Uh, update was it here.
So this is the beginning of a new frame.
We are having 1,02.
We get 125 prefabs per thread.
which I guess makes sense.
Now one thing I should say I am not sure that I have I'm not sure that the this is memory safe. So it might be that the couple of uh objects that are updating and flickering like that are that they are just happen to be on the boundary and the threads are like overwriting. But I wouldn't expect that because all of them are starting at the beginning of the index and working their way down. By the time it gets to the 125th object, the the other thread should have been way past that.
So I'm not sure.
Uh it's definitely not zero.
We start at zero.
This is not more than the maximum.
We say arcs job index. Oh.
Well then, that's an issue. [laughter] Hypo.
Okay. So the the only thing was that typo, I guess. Now everything looks normal.
Beautiful. Now I think we're still like kind of spinning the other Well, not spinning. The the the issue is that I am just sleeping for 100 milliseconds.
Uh down here somewhere.
this one after I have processed for one frame. So the main thread goes to the next frame after uh 33 milliseconds. So it will be alone the next time the the next two three frames and then it gets the help from the other threads and then you know again they're sleeping. So actually we need to continue this. We are I want to do the right now we did the um function calls later we will do the uh now we will do the physics and lastly all these threads needs to be having a sleep work right so really what I want to do is I want to delete delete this all together. And now we're going to be uh utilizing a lot of uh C CPU just to do this.
So it looks the same.
Maybe even the stream is struggling because I'm probably using a lot of uh resources.
Yeah, a lot.
I I can't tell you the exact value, but it's it's definitely a lot.
You're trying to teach yourself several physics algorithms, but making game engines are way out of your league.
Well, the the good thing about physics in games is that like most of it is fairly simple. If you want to get like really deep into it with light and stuff like that, then you you can get very gnarly. But all of the physics that I'm using here so far is just very simple like multiplication of vectors and most of it I'm using uh I'm using a math library for it anyway.
But you know I went through university I did my matrix multiplications by hand for two years. So like I've been through the pain.
So I I I would consider myself uh being able to use it in good conscience.
If I pay for 100% of the CPU, I'm going to use 100% of the CPU. Exactly. Well, 100% of the CPU, I would like the OBS to be using some of it. So developing the game, I want to I want to limit the game so that I can do other things but still use 100%. Uh anyway, so I would say this seems like it's working.
So we're going to do the same thing for this one. I can just do on the main thread. I I don't see a reason why we would do it. Um why we would divide it, at least not now. We can we could think about profiling this and see if it actually matters, but I'm I'm going to ignore it for the moment. So then we do regular prefabs, not including UI, and we're doing physics here.
So I'm going to add this also to my prefab functions.
Now it's important I'm having these uh function calls and then I'm doing delete and then I'm doing physics. I could throw this in here, but it means that if the prefab is deleted by the function, then it's not really necessary anymore. And see, here's the thing.
I throw all of this work on the C on the thread Q and each function maybe it will spawn another entity and that could be fine I think but what you don't want to do is you want you don't want to delete something in the middle of something being worked on. So that's why I separated the function calls initially.
But if you are limiting the function here to be something that can't spawn prefabs, then you I guess you could just uh throw it on there.
I'm going to keep it separated for now.
Or maybe see this is an interesting problem. This is why maybe sometimes you don't want to do it like that because I want to batch everything as much as possible.
But I could have just the main thread go through one entity, right?
No, that wouldn't work either. It would be the same problem actually. It wouldn't solve anything to process each entity or prefab individually.
I am a little bit scared about the consequences of this.
It should work as long as I'm only pushing things and deleting them afterwards because let's say I have a hole in here.
Let's say these are just entities 1 2 3 4 and I happen to have a hole right there.
So uh actually this would be the one.
So this is seven. Here's six here.
Here's five.
So let's say I happen to have a hole here. Suddenly one entity is pushing another entity eight here in in the free hole. And I have my thread. It's going through its uh you know assigned memory and it just happens to uh allocate this before it gets there then that's fine.
If it happens to be a hole here and it goes through then it just skips it over and continues. That's also fine.
The issue would be I think what I'm getting at is that if I create or delete entities, I need to do that after I have done all the updates.
So I cue all of the creation and deletion to another step entirely and any updates to the entity creation or deletion will be on the next frame.
Make sense?
So that means that actually we should run the physics in the same thread.
So where is this? down here.
Yeah.
If there's a function, we do this. If there's children, we do that.
cell size.
Oh, Okay, cool. So, I'm I'm I'm a little bit scared of creating and deleting entities with multi-threading because it means that they need to be assigned an um like okay, you could say that creating a prefab or entity is a scheduled task, but the main thread is the one who is deciding the uh index where it goes.
So that would be fine I think.
[sighs and gasps] But I wonder about this uh if I screwed this up.
Should be fine. And we we will notice it if something is wrong.
What's this error? Ah yeah.
Cool.
And then we have UI components. Let's ignore that for now.
And here again we we need to allocate all of this stuff.
So let's ignore this also.
Maybe we just see what's going on.
I feel like it should be the same as before.
Oh yes, but it's stuttering.
Why is it stuttering?
Let me try something.
You see it right?
Uh what am I working on right now?
General thread jobs. So I have a library that I wrote for pushing tasks, popping tasks off the queue. And then I also have this function process all which is just running the task pop function infinitely until uh there's no more tasks.
And at the beginning of the game, I create as many threats as I count as as many threats as I can. I I um I reserve one task to be my graphics uh thread and the rest for now are worker threads for me.
And I'm trying to just go through all of the prefabs which I it's basically like an object or node or you know something like this.
So a prefab it's it's like something that has a position and then it has components inside or entities that that can be uh meshes and things like this.
So I'm going through all of those and they can have functions assigned to them. So maybe I run the functions and then I'm going through the children who might also have you know here a mesh or another function again.
And then later I'm doing a physics update. So I'm just trying to schedule.
This is all of my prefabs and there's about a thousand of them. So I'm slicing it up into uh 125 because I have eight threads with the main thread.
So these are 125 entities and was scheduling that as a task to process all of those as a chunk.
And I do that eight times. And the last one is for the main thread to process.
And what I'm doing right now is I am going to add the sleep to the threads because at the very end of the game uh loop I'm locking a mutx and then I'm copying over some indexes and memory uh to swap the memory with a gra graphics thread.
Then I'm measuring the time to synchronize and I get this uh left to wait. This this is how many well this is how many seconds to wait. I have this milliseconds to sleep. I guess I also need to push that to each thread.
So, I'm wondering, is it safe to just push seven sleeps?
Oh, also I just realized My thread count is not actually eight because I have the special thread. So the the number of worker threads I should actually add that Recount.
So this should be six right now.
Let's double check that.
Yes.
Very good.
See, I want to put all of my worker threats to sleep.
So, I think all I need to do is add a job for each of I need to make this a function somewhere.
I guess I don't need to do that myself.
Had I use KB text shape for font rendering?
Well, I'm using I am using KB text shape for text shaping.
So you can see it in my dependencies here.
KB text shape and then I'm using um uh STB true type for rendering the font or rasterizing the font rather. And then when it when I have the shape and I have the raster, then I am uh I can draw it using OpenGL. if you want to check that out because I I spent like an entire week or two on it.
But on my YouTube, you can scroll down and I am doing it here I guess.
No text layout.
I'm doing text layout. Text layout. So all of these are me learning and using text KB text shape.
Uh, I haven't noticed any slowdown, but I am also just I'm running it once and then I'm using the same settings for any shaping that I need to do later.
Uh, yeah. So, we are doing a 4 count work threads.
arts.
So, I actually need to use this is very annoying. I was talking about this yesterday, but I think there's some issue with my with my motherboard possibly.
But I have this issue and I had it during multiple installs where my CPU is always running at like 99 to 100. It's not actually running at 100%.
It's just reported erroneously. It's It's wrong. Something something in Windows is broken. And when I reinstall Windows, it works for a while and then after a couple months, it just comes back. And I'm not installing anything weird. I I just have, you know, Firefox, VS Codium, Steam games, couple of Steam games or something like that. Like I'm not downloading anything pirated or anything like this. So I I really don't think it's a virus or like Bitcoin miner. And when I open up open hardware, how how do I open it?
Is it not installed? I installed it yesterday. No.
Open.
Yeah, I need to use open hardware monitor to get the true uh load on my CPU.
Here we go. For some reason, it's not showing up on my uh install programs, but okay, here we go. So, here we have open hardware monitor. It's running.
It's saying h is it 20 or is it 90 16? That makes sense. 20%.
So, yeah, hover around 20%.
And then we can run the game and see if it stays there. Oh, no. I'm definitely uh not staying staying at 20. Did I even compile it?
I did not compile it because I have an error.
Thread sleep. Huh?
Is it conflicting types? Yeah. Am I even using this anywhere?
Uh, no. I I don't think I'm using this. I think that was something I wrote once and never used.
So that's gone.
Threads sleep.
You are gone.
Why can I not just call sleep?
Why is it poisoned?
Sleepy time.
Uh.
Oh, I I know now.
I I do need thread sleep, but now I have conflicts.
So I I do need it, but I need So I need to give this a different name.
Mhm.
So you can go in here. Yeah.
in global. No, that's true.
But doesn't need to be a global almost guys.
Nonvoid does not return a value.
Okay.
Still linker error. Thread process all.
Oh no.
Wait, what?
Did I delete this?
It was right here.
Oh no.
Um. Whoops.
And I can't go back.
Crap.
Uh. Whoops. I've never had that happen before. That's a little bit annoying.
So, I guess I have to uh go to the uh get and just grab it again.
Whoops.
Game bread run something.
It's just this Okay, it's stuttering and well, it's not blasting my CPU.
But it started to stutter and I I think actually I know why.
So I'm guessing the reason why is because in the game what what I did essentially is I added multi-threading on my game update.
So, I'm pushing all this uh prefab update function calls, which again, I take all of my prefabs and I chunk them into slices and I push those and then I update them. But then before I actually verify that all of the threads are done and ready, I just proceed.
And I think that's the issue. What I need to do is make sure that all of the worker threads are done.
Or actually I have the thread Q and I have a read and write index.
So maybe we can do atomic read.
read index.
Same for rights.
And we check if We we do this because when we are popping when we're pushing the thread uh when we're pushing the task what do we do? we reserve a write index and when we are popping a task we reserve a read index.
But if it if the queue is empty, the read index is the same as the write index.
So, I can just sit here and wait until those are the same because no one else is pushing work on the queue.
And I can actually do it like this. So, while there's still work, we process.
And then I make sure that they're all done.
See, but it should still have worked because in this process all we're already having an infinite loop and then we break when there's no tasks.
So I already I should have already accounted for 632 632 we break immediately.
So, that can't be the issue.
It's definitely stuttering.
Let me see on the stream if you can see it.
I think you can see it. Yeah.
Maybe we need Tracy for this and see if we see any spikes.
So, how did I use Tracy again?
I should mention it was buttery smooth until I added the multi-threading for the worker threads.
Unused variable.
Sure.
unknown argument. That's fine.
So Tracy H.
Tracy, give me Tracy. I'm looking for Tracy. It's in dependencies. Okay.
profiler.
Uh crap.
I I completely forget. I I built it and I forget where the Tracy client is.
I'm dumb. It's It's right here.
Now, is it capture or profiler? I think it's profiler.
Connect. Here we go.
So, what's going on here?
43 milliseconds.
Yeah, you can see that there's lag spikes for some reason.
82 milliseconds. What is going on here?
Wait, I think these frames are wrong.
Uh Hold on.
Uh, that couldn't be. That's fine.
Profile framework.
This one is unnecessary.
So for those familiar this is the main window loop or for those not familiar this is the main windows loop. We are processing windows messages.
Then we call game run and then we mark the end of the frame.
And what does game run do? Again, we are updating the game, drawing prefabs, drawing UI, and then we're doing this mutx swap to swap the memory that the graphics thread is using.
And then we do the synchronization to sleep for a given number of milliseconds. And let me actually just look at the output here.
And you can see that this is fairly consistent. But for some reason, seven five we did not have that issue yesterday.
And I can't see why adding more threads to do more work would cause more slowdown.
See? Yeah. Let's I think that you have a good uh a good point. Iron Iron Nev. Is that good? I think you have a point.
One thing that we can do to test this is to go into the game and I want to disable these uh not those actually.
Let's go to the thread process all call.
Let's just disable this one.
Now again, we're going to proceed before all the stuff is running, but it should take care of the lag. But it doesn't.
You see?
So what we found yesterday is that doing all of this with one thread takes about two and a half milliseconds.
I am dividing the work so that each thread has 125 entities to update and that makes the entire thing overshoot I guess.
Time in seconds. Wait.
Let's print this.
6F.
because I I would be very surprised if we don't reach this earlier.
So all I'm doing now is I'm printing the time when we reach this u end of the frame and then I'm also printing the time after the synchronization and I need a slashn So what we got there? The time actual work is 1.6 6 milliseconds 1.8 2.3 3.9 Is it growing 6.3 3.6 six. I don't see any particular pattern with it. It's a little bit unstable, but you know that might just be because again the they are fighting for the same objects or something like that.
But then the weight is completely out of whack.
32 31 No, wait.
That's still less stable than I would like it to be.
No, it's still saw that here. It's almost 5 40.
There's a lot of frames where it's just randomly stalling.
So why would that be?
Did I change something in the sleep?
No.
So to reiterate the issue right now when we get here it only took 2 3 milliseconds sometimes a little bit more but that's never mind because when we get here we still need to wait until 33 milliseconds and I'm doing nothing like I am do literally doing nothing. I'm only doing the same thing that I was doing before.
Actually, let me try something.
I'm doing the same thing that I was doing before, but the the threads should do nothing. They should be maybe spinning in the background, but like that's it.
Uh let me do something.
This is essentially disabling what I have done the entire evening.
So we have we are scheduling all of the things at once and then we never push it or well we can push it but I'm going to disable in pain.
So now we should have zero worker threads or rather I would like to disable them here.
They're doing nothing and only the main thread processes the queue and then it comes here and it sleeps. So it should be identical to what we were doing before completely stable was like one that was Huh.
I don't like that.
But overall very stable.
So that's what I would like to see. So I don't understand if I do this Nothing is different.
That's exactly the same.
Yeah, I'm h I'm having some other bugs.
I I don't know how this happens but so this is completely stable.
I mean as stable as you can do with sleep.
And the only difference is that we're scheduling everything as a single job rather than multiple jobs.
Oh, I don't know. I was thinking about something, but I I lost it.
Yeah, completely unstable. Let me check this again.
Or maybe Even So now everything will be on on one line. It's stuttering like like trash.
And let's see.
we are having well for for one it's less stable overall but is there a pattern here so this is 33 it took two milliseconds here it also took 33 but for whatever reason it took 15 milliseconds to get there and this one is 46 it took 14 milliseconds So definitely I'm doing something screwy with the threads and they are fighting for the work or something and uh that's causing delays. Now, why it's causing delays when I'm sleeping?
Possibly Windows is not returning my thread on time because I'm using too many resources and I'm not letting them sleep. Or even if I am letting them sleep, something is going on. Maybe not all of them are sleeping.
Maybe one thread takes two of the sleeps and then another one doesn't get one. I I don't know.
This is not a good way of doing it. I should just like have a way of telling each individual thread to sleep.
But I was thinking that I was being smart. But you can't outsmart the computer sometimes.
Yeah, that's better. Maybe until you start spinning around. Uh, so when the the when the threads are sleeping, it's better, but it's still not good.
And there's still spikes that go like 40.
So yeah, I think I'm going to have to call it the night there. Uh, so thank you guys for watching.
We will be working on this uh further.
But yeah, thank you for watching and uh I'll see you good night.
Videos Relacionados
Agentforce NOW AMA: Build with React and Salesforce Multi-Framework
SalesforceDevs
490 views•2026-05-28
How agent o11y differs from traditional o11y — Phil Hetzel, Braintrust
aiDotEngineer
450 views•2026-05-28
Re: 🗣️📍theprophedu📍2026 GST 103 CLASS (E-EXAM REVISION)
theprophedu
636 views•2026-06-04
WEB TECHNOLOGIES UNIT-2 | Degree 4th sem BCOM Computers web technologies unit-2 full explanation💯✅
LearnwithSahera
1K views•2026-05-29
More tests are always better? How to use AI to identify tests that bring little value
Alliance4Qualification
335 views•2026-05-29
Search Algorithms Explained in 60 Seconds! 🤖💨
samarthtuliofficial
218 views•2026-06-01
People of Game of Thrones using JavaScript DOM
AltCampus
296 views•2026-05-30
Instagram accounts got PWNed
EricParker
13K views•2026-06-03











