So that's interesting. So I guess, so that means this is gonna be like result is this and then we're gonna wrap up with the result and then we're gonna do if let AI is result. I guess we'll see exactly how this pans out. I'm not sure yet. But so we do the CAS execute and whatever result we get back, whether that is we succeeded all the CAS is or we failed at some point, we call wrap up with that information and the CAS descriptor. Its output is either the operations result which is returned to the owner thread or an indication that the operation should be restarted from scratch from the generator method. Okay, so wrap ups output is the operation result. So wrap up is the thing that should return self output or an indication that should be restarted. So it's a result of this and unit. Great. And so then down here, what we're actually gonna do is we're gonna, I guess, like this is really gonna be a loop, right? Where in fact, maybe this is just a for retry in zero and onwards. And if retry is zero and once in a while. So if it's the first time, then we're on the fast path and once in a while, which is, I guess, help is gonna be once in a while, false. So if retry and help, then we're gonna do self.ifretry is zero, then if help, sorry, I know that syntax highlighting gets in the way here. It's gonna be better in a second. Why does it not? There are too many things. This is gonna make it happy. Great. So if we're on the first iteration through the loop, so I guess now we can get rid of this thing. If we're on the first iteration through the loop and we decide to help, then we do help. If we're not on the first time through the loop, then there's probably gonna be something else around helping, right? Cause this is the case where we failed once and now we want to help. We want to help more aggressively maybe. I don't know yet. So we're gonna match on the wrap up and if it's okay with the result, if it's okay with I guess the outcome, then I guess we're gonna break with the outcome. We're gonna break out of the retry loop and if it's an error, then that said we should retry from the generator which I guess just means continue. And I still, I don't know where this ask for help business has kind of come in yet. I guess that's something we're about to find out. All right, fine. I guess loop and let me fast is true. If fast help more, fast equals false. And now maybe this can go away. Now, yeah, this looks like the kind of structure thereafter. The generator and the wrap up methods are parallelizable and they have an associated contention failure counter. Interesting. So the contention failure counter actually then sounds like it's passed to the generator and also to the wrap up, which I guess is so that they can also detect contention. Like it might be that you can detect contention without necessarily doing the sort of owner cast as they call it the commit point. And we wanna be able to detect that too. So this is gonna take our contention counter and so is this. And I'm guessing there's gonna be an if the contention is big enough for something down here, then we're gonna try to get help. Finally, we require that the castes of the generator method outputs be for fields that employ versioning. That is a counter is associated with the field to avoid an ABA problem. The version number in the expected value field of a cast of the generator method outputs cannot be greater than the version number currently stored in the target address. This requirement guarantees that if the target address is modified after the generator method is complete, then the cast will fail. Okay, so this is kind of subtle. And how are we gonna implement it is sort of an open question for now. But the basic idea here is that we want to make sure that what's the best way to describe this. Let's say here that, in fact, let me explain this with a code comment because that seems nice. Okay, so, oh, stop trying to be helpful editor, please. Thank you. All right, so let's say that we have let's say that the head pointer of the linked list points to some node A. And it's at, let's say, pointer address like 0x21 doesn't really matter, which some pointer address. And what we're trying to do here is a cast where we're trying to insert B, which is at 0x2, right? And so what we're gonna do is we're gonna do a cast of head from 0x1 to 0x2, right? So here, I guess B.next is 0x1, right? So B.next points to A. So we're gonna insert B at the head of the list and have its successor pointer point to A. And that's the way you do a push to a linked list, right? And what we're gonna do is we're gonna cast of the head pointer from 0x1, which is currently points to A, and make it point to 0x2 instead, assuming that the head pointer is still 0x1. And we want this to succeed if A is still at the head and we wanted to fail if A is no longer at the head. And that is because B.next would need to be different, right? We need to update B.next if A is no longer at the head. Otherwise, what's gonna happen if we do the swap, right? So we stick B at the front, then now if this was no longer what was at the head of the list, like let's say someone inserted a C, we would basically be overwriting C with B. Like the head of the list would now be B, B's successor pointer is A, and C is nowhere to be seen. And that's the problem, right? So that's sort of the basic idea of CAS. But there's another problem that arises, which is a little awkward. Imagine someone else CAS' meanwhile, very ominous. There's an insert C, which is gonna be, let's say C is gonna be as 0x3. And C.next is correctly gonna be 0x1. We're gonna, that operation is gonna CAS head from 0x1 to 0x3, so long, so good. If that succeeds, then this CAS will fail because 0x1 is not equal to 0x3. And so that's all fine. Now let's imagine someone removes A, right? So they do a CAS of C.next from 0x1 to a null pointer to indicate that this is the tail end of the list. That's all fine. Now let's say someone inserts D, and D is assigned the address of the old A, right? Because we removed A, so let's say A got dropped, right? Like now that memory is reusable. So now there's a 0x1 here that's actually D, and D.next is gonna be 0x3, right? And now that operation is gonna CAS head from 0x3, which is C, to 0x1. That succeeds because C is at the head. I'm still imagining that this CAS is like being delayed or whatever, right? This operation succeeds. So now the list we have is D, which is at 0x1, which points to C, which is at 0x3, which points to nothing. And now, right? And now the conclusion, which is this CAS that was originally insert of B continues, right? So it tries to do this CAS again, and the head of the list is now 0x1. Like the values are the same, and so this CAS is going to succeed and set the head pointer to BB. So now what we have is we have a head, which points to B, which is at 0x2, which points to D, well, which points to A at 0x1, but that's actually D because that's also at 0x1, which points to C at 0x3. Now in this case, it actually sort of works out because the addresses happen to be similar, but imagine the B also encoded, say, what's a good example of this, like let's say that this was a doubly linked list or something, or better yet, let's say that B included like the title of next item, and it would encode A, even though the next one is technically D, right? So basically we had a CAS operation that succeeded because the values happened to be the same, but the thing that we're pointing to aren't really the same. One was A and the other is D, and even though they happen to be at the same address, they're not the same. And so we really wanted this CAS should have failed. And this is known as the ABA problem of a value was A, it became B, and then it became A again, but from the point of view of this other insert, that should be considered the value has changed so failure operation. And so what they're getting at here, sorry for the lack of warning, is in order to solve the ABA problem, we wanna counter associated with any given field to ensure that the ABA problem can't happen. Basically, if the A allocation gets reused, then it must be given a different value. So even though the pointer is the same, like D gets allocated again, we require that the CAS fails if ABA has happened. There are a couple of ways to do this. You could imagine that you manipulate some bits in the pointer to include a counter there. So even though, if we go back to the terminal for a sec, like you could imagine that even though this is 0x1, maybe it's like this is 0x1 and A was actually like 0x1, right? So notice this doesn't have a one here. And then if, so when the allocation got reused, we also set this extra little counter bit in the pointer. So that way, this is actually a swap from 0x00001, which is not the same as this one. And then we have some logic for figuring out, like this value, if you wanna use it as a pointer, just disregard the first few bits. So one way to go about it, there are other ways, we'll look at it when we get to this, but basically the problem we're trying to prevent here, and what they're getting at in the description is, you need to guard against the ABA problem. And we are going to do that in some way, and in order to sort of follow this. Okay, so this is their sort of definition of the normalized representation. And that is now what we reflect as well. They haven't actually said how the simulation of this is gonna work, right? As we saw at the top of this section, they say we later show how to simulate this. And that's sort of what we're running into in our code, right? Which is, we now have the trait, right? This is the trait that they're telling us that we need to use. Like they're not necessarily dictating we use these associated types and stuff, but this sort of the structure of it. And then we've sort of made some assumptions about what that simulation is gonna look like. Like we think it's probably gonna have a loop like this and some helping, but they haven't told us yet. And I do find it really useful to sort of write out what you think it's gonna be before they tell you, and then try to adapt your understanding based on that. Okay, I talked for a while. Let's do some questions for a sec before we continue. Shouldn't the contention counter not be mute as the methods are parallelizable? So I think, I could be wrong about this, but I think the contention counter is a local measure of contention. So multiple threads might execute generator at the same time, but each thread is gonna have its own contention counter. Does that make sense? So that's why they have, it takes an immutable reference to self, a shared reference to self, because multiple threads are gonna call CAS executed once, but they're allowed to have different contention counters. And that's why they take immutable reference to basically this thread local value. Why do we count the contention failures for generators and wrap-ups together? I think the idea here is that you want to count anything that resembles contention, and you might not necessarily in advance know where that's gonna be. Like for some algorithms, maybe contention can only be detected by the owner CAS and cleanup. Sometimes it can be detected before you do the owner CAS too. And so you want to give the maximum flexibility to the algorithm to say whether or not there's contention. Yeah, debugging ABA bugs is a huge pain. It's super painful. Yeah, it's generational pointers, generational indices is one way to think about this. What would you see in the outputs that would indicate that you have an ABA issue? One example of this is imagine that you have a linked list of boxed-in values. And so even though the pointer is the same, the V-table is different. They're just like different objects. And I guess it would still be fine for, the V-table's will still be in the right place that would work. Yeah, the problem occurs when what is encoded in the node whose CAS should have failed includes information about the successor, like the target of the CAS.  And so it being reused isn't really a problem. And in fact, this is something that designers of linked lists have taken advantage of is that in the linked list you don't need to care about this. But there are other cases where you do need to care about this, which is like in a, let's go to example of this. In a binary tree, for example, if the successor, like you have a left and right pointer, and even though the pointer of the thing that is next on your tree is the same, it's contents like it's left and right might be different because it switched from like A to D for example.  And so you really need to like reconsider whether this is even the CAS you want to do. And so you actually need to go back to the generator that's the sort of the architecture that they're laying out for us that if this CAS fails, the CAS is the wrong one. You need to, maybe you need to insert somewhere else in the tree instead, but you don't learn that that is the case because the CAS seems to have succeeded or the CAS did succeed, it just shouldn't have. The ABA problem would not occur on ARM because it uses LLSC. I think that's true. Ooh, is that true? No, it's... I'm not sure. So the biggest difference between the locked load store conditional in ARM and compare and swap is that the conditional store is allowed to fail spuriously. Spuriously in the sense of the expected value might still be the same, but it rejects the change of the store because for example, some other core has tried to modify the value, but it modified it into the same thing. So in that sense, it would deal with the ABA problem. I don't know whether a store conditional will fail spuriously, spuriously if the current core is the one that did the change. So imagine that two threads are running on one core on ARM. One does a load locked, a different thread, and then it gets scheduled out, a different thread runs on the same core, does a load locked and a store conditional on that value, setting it to, let's say the value is A, setting it to B. Some third thread runs on the same core, load locked, store conditional, stores A again. The original thread comes back and now gets to execute its store conditional. Does the store conditional fail? I honestly don't know. I think it only fails if some other core changed the value, but I'm not sure. I think the ABA problem still applies here. Oh, YouTube broke, that's sad, but Twitch is still here. But yeah, basically I think this problem still applies on ARM. Okay, all right, let's go back and see how they actually implement the actual simulation code. I need some nuts first. All lock-free data structures we are aware of today can easily be converted into this form. That's good. Yeah, in fact they show all abstract data types be implemented in a normalized lock-free data structure, but the universal construction is likely to be inefficient. This is similar to the argument for the universal simulation that we talked about in the beginning, that you can make any abstract data structure wait-free this way, but the finite number of sets might be a very large number and you might not actually want to do it. Intuitively, the first, the generator method can be thought of as running the algorithm without performing the owner cast is the actual commit points. It just makes a list of those to be performed by the executor method, and it may execute some auxiliary castes to help previous operations complete. Yeah, so they give the example again of the linked list where the generator is gonna be the one that does the search of the linked list and while searching, it might also remove nodes that have been marked as deleted in the past. Ultimately, the generator returns the node to be deleted. And then the cast that gets emitted is the one to mark that node as deleted. So the generator doesn't execute the owner cast, but outputs it as a descriptor to the cast executor. And if there's no node to be removed, there are no castes to be executed and the generator just returns an empty list of castes. This is another reason why the container might, why it's a good idea for the container to be generic, is that in the linked list case, it sounds like, for example, you might actually want the cast descriptor to be an option cast descriptor, because it could be none if you don't find a node to remove. The cast executor method attempts to execute all the owner castes in Harris's linked list, like in most known algorithms, there's only one owner cast. So basically like you shouldn't need a vector of cast descriptors or anything, but if you do, our interface supports it. The cast executor method attempts the owner cast or the multiple owner castes one by one until completing them all or until one of them fails. So that's the sort of, it returns a result where the error case is the use size, which is the index of descriptor that failed. After the cast executor method is done, the operation might already be over or it might need to start from scratch. Typically if a cast failed or some other auxiliary castes should be executed before exiting. The decision on whether to complete or start again and possibly further execution of auxiliary castes done in the wrap up method. And Harris's linked list example, if the generator method output at no castes, then it means that no node will be with the required key exists and the wrap up method should return with failure. If a single cast was outputted by the generator but it's execution failed in the executor, then the operation should be restarted from scratch. Yeah, so this is like if there's contention on the head or the ABA problem happened, then we wanna run the generator again and then try the castes a second time. Finally, if a single cast was outputted by the generator and it was successfully executed by the executor, then the wrap up method still needs to physically remove the node from the list, which is the auxiliary cast and then return with success. We note that that's not super important. That's fine. All right, we're now up to the transformation details, which is basically an explanation of the algorithm itself, like how does the simulator work? All right, so since we're on a new chapter, let's do a quick bathroom break here too for before we start getting into the generator itself. So don't block on me, but you may have to wait. I'm back. Mission wait-free failed, it's true. Yeah, isn't it such a cool research paper? Like, we're very in the weeds of it, right? But if you take a step back, it's really cool what we're doing, right? We're just taking algorithms that already exist that are lock-free and just like magically making them wait-free. It's really neat.  Unfortunately not.  So you either need to do bit fiddling, which is tricky because there's like a limit to how many bits you can actually tweak in a pointer before it gets a different meaning. The way, so I've sort of cheated a bit and looked at the Java code implementation of this and what they actually do is you do a double indirection.  And you do a cast of the pointer to the intermediate object and that gives you this versioning. All right. You can see my screen again and take it. Just this first sentence. It's like so neat. I think they must be, they must have been very happy when they wrote this first structure. When they wrote the sentence, they must have been like, yeah, we did something cool. To execute an operation, a thread starts by executing the normalized lock free algorithm with a contention failure counter checked occasionally, what does occasionally mean? To see if contention has exceeded a predetermined limit. To obtain non-survation, we make the thread check its contention failure periodically. For example, on each function call and each backward jump. If the operation completes, then we are done. Otherwise, the contention failure counter eventually exceeds its threshold and the slow path must be taken. Okay. So that's interesting. So that suggests that the contention measure is constructed at the top and we check it like, I guess, so use slow path. So it suggests that we should do this for each function call. So that would be something like this. And on continue, which would be here. And I guess we don't actually know what that's gonna do yet, but use slow path. And I guess that's just gonna be if self.zero is more than threshold. Threshold. I wonder if the different thresholds might make sense. Let's set it to two, I don't know. If different thresholds might make sense for different data structures, it could be that this is a good place for something like const generics. Where like, maybe we should have the weight-free simulator be like const generic over the threshold of the contention counter. It should be pretty damn cool. But that's sort of a consideration for a different day. That would be really cool. That's totally what we should do. Yeah. So I guess the idea is that we're gonna just check the contention counter every time we go somewhere interesting. I guess it doesn't really need to be here. It could be just after every method call. And that would probably be good enough. Because the top here is the same as the bottom, right? Cause it's a loop. So if the operation completes, then we are done. Otherwise the contention slow path must be taken. So we don't actually know what the slow path looks like yet. There's also a possibility that the contention failure counter never reaches the predetermined limit for any execution of a single method. That the wrap-up method constantly indicates that the operation should be restarted from fresh. This must also be the result of contention because if an operation is executed alone in the lock-free algorithm, it must complete. Thus, the thread also keeps track of the number of times the operation is restarted. And if this number reaches the predetermined limit, the slow path is taken as well. Oh, interesting. So there's actually two values here. So this is internally per loop. Is this contention counter? And then it separately says, track the number of retries for retry in. And this is going to return outcome. And this is unreachable, which is interesting. I wish it could just do that for us. And I guess now fast is just retry equals zero. There might not even make sense for this to be a separate case. I don't know yet. Yeah, so it sounds like the contention counter is one thing. And then there's also like if retry is more than retry, retry threshold, then also slow path. And retry threshold and I guess contention threshold. All right, the key point, and this is something we've talked about in the past, the key point is that an operation cannot execute infinitely many steps in the fast path. Eventually it will move to the slow path. Okay, so maybe what we do here is that we say that the slow path is after the loop. Maybe that's what we do. So maybe this is fast path and then use slow path is just gonna be like break. Yeah, so I guess this means that the algorithm is set up so that you can retry the fast path, but at some point you move to the slow path. And that's based on contention or if you just retry many times, which is also really contention, which I guess means that this code that we had for the slow path is gonna go down here somewhere. I mean, we don't actually know what this looks like yet, but I guess I don't know what I is gonna be yet either. So there's still something missing down here. I guess let I is zero because I don't know yet. The slow path begins by creating an operation record object that describes the operation it is executing. A pointer to this operation record is then enqueued in a wait-free queue called the help queue. Okay, so they call this an operation record. The thing that we called help, they call it operation record. I'm just gonna use the same name for now. I think we can always change it later, but I've found that while you're going through and doing this implementation, or doing this sort of porting from the English version to the code version, using the same names helps a lot in ensuring that you're using the correct abstraction and the right abstraction in the right place. And then we can always change this later on to be something more rustic or something that feels better for documentation or something, but at least we get this direct sort of translation or closer correspondence between the text and the actual code. All right, so we're gonna create an operational record that describes the operation it's executing, which we don't quite know what is yet. A pointer to this operation record is then enqueued in the wait-free queue called the help queue. So that's, we create the operational record. We add that to the help queue. We add a pointer to that to the help queue. Next, the thread helps operations on the help queue one by one, according to their order in the queue until its own operation is completed. So I guess, let's say this is a little clearer if we call this help first, right? So it says until its own operation is completed, we're gonna help according to the order in the queue. So while we haven't completed, we're gonna help the first thing in the queue. And then we're just gonna go around in a loop like that. Threads in the fast path that node is a non-empty help queue provide help as well before starting their own fast path execution. So that sounds like they always help. Which also seems a little surprising. But I guess the help queue could be empty, right? Like maybe most of the time when you go through the help queue is empty, so you just, you just sort of check and there's nothing there, so you keep going. Great, we still haven't figured out how we, like let's say that our thing is completed, how do we actually get out the return value? Cause maybe that's executed by some other thread. It sounds like the sort of operation record needs to include some information about like the current state of the helping and potentially the operational outcome. The help queue and the operation record. Yeah, so the wait queue is the one that we're going to have to implement. I guess what we should probably do, and this is instead of calling this help queue, we should call it like wait free queue. Let's keep help queue, that's fine. It says the queue in, I guess in this reference supports the standard NQ and DQ operations. We slightly modified it to support three operations. NQ, peak and conditionally remove head. Add, so NQ, peak, so peak and try remove front. I think we got pretty close, that's pretty good. And I guess we need to make this add down here, be NQ just to sort of match. You'll notice I didn't rename the try remove front. That's mostly just because we already have rust parlance for these terms, it seems fine to reuse the same one. NQ operations and queue a value to the tail of the queue as usual. The new peak operation returns the current head of the queue without removing it. And finally the conditionally remove head operation receives a value it expects to find at the head of the queue and removes it only if this value is found as the head. In this case it returns true, otherwise it does nothing and return false. Okay, so this should return bool. So this should probably be maybe remove front. Because try is usually associated with the result type. I mean, actually I feel like try remove is still the right one and then we can just have it be result, nothing, nothing. Like that is an encoding of a boolean, right? And it either removes the thing you told it to remove or it doesn't, like it either succeeded at removing or failed at removing. I feel like that's the right sort of encoding here. This queue is in fact simpler to design than the original queue because D queue is not needed because peak requires a single read and the conditionally remove head can be executed as a single cast. Oh, so this is important. Peak requires only a single read. We haven't looked at the implementation of the wait-free queue yet, but this is important because remember in the fast path we're gonna check whether we're gonna call peak basically on every fast path. And so if peak was expensive we would be making the fast path a lot more expensive by having to sort of check at the head of the help queue every time. But they're saying the peak is a single read. So this sort of suggests that if the help queue is empty the fast paths will remain fast. And conditionally remove head can be executed as a single cast. Therefore conditionally remove can easily be written in a wait-free manner. So that's nice. Remember we need the help queue to be wait-free otherwise the whole sort of tower collapses. Some care is needed because the interaction between queue and conditionally remove head but the similar mechanism already appears. The Java implementation for our variation of the queue is given an appendix A. Okay, so basically we're gonna have to implement this I guess to do implement based on appendix A. So that's a pretty big to do we're gonna have to do. We use this queue as the help queue. If a thread fails to complete an operation due to contention it asks for help by enqueuing a request on the help queue. This request is in fact a pointer to a small object that is unique to the operation and identifies it. It is only reclaimed when the operation is complete. In this operation record box object there's a pointer to the operation record itself and this pointer is modified by a cast when the operation status needs to be updated. All right, so that's a little involved. So they have an operation record box that has a value that is a pointer to an operation record. Why? Ah, I think I see why. So really what this is saying is that there's an operation record box which is gonna be sort of a, let's use the same words as they're using. So an atomic pointer to an operation record. And then there's a operation record which is the thing that actually holds the stuff. And these don't actually need to be pub, I don't think. We need atomic pointer. So the operation record box doesn't actually need to be box. Like this is one place where we're gonna want to change the name. It's totally fine for this thing to be on the stack of the owner, for example. What's important is that this basically means that all of the, like you put this pointer on the help queue and that gives all of the threads access to the same atomic pointer that they can use to update whenever the sort of status of the operation changes. And in fact, this operation record thing, my guess is this is basically gonna be like RCU. If you're not familiar with RCU, RCU is read copy update. So the idea is that if you have multiple threads contending on some resource, one way to get them to operate concurrently is that they both read the current state. They both copy the current state. They both update the copy they have of the state and then they both try to sort of atomically swap in the updated state back. So I'm guessing we're gonna end up doing the same thing here. That if two threads both try to help, they're gonna read out the current pointer to the operation record. Then they're both gonna do whatever the next operation is and update their operational record. Like they're gonna take it out of here, clone it, modify it to represent them having executed the operation and then try to sort of atomically swap in the updated value back here in the box. So that over time, like the box will eventually point to an operational record that has completed set. So rather than all of these threads updating these in place, my guess is this is actually gonna end up being a bool and this is gonna be a U-size because it's gonna be read copy updates. So every thread that decides to help is gonna clone this thing and write back a pointer to its updated clone. There's gonna be, deallocation is gonna be a pain for this and it's also a little unfortunate that we're gonna end up with more allocations but maybe that's okay for now. I don't wanna deviate too much from the design and part of the reason for that is the design is already complicated enough. I don't think we should try to optimize design while trying to implement it. I think we should implement it first, get it right and then we can optimize if we see that this becomes a problem. All right, so down here, we're actually gonna construct a operation record box. Just gonna have a val, it's gonna be an atomic pointer new, which is gonna be a box into raw of a box new off an operation record of completed false at I, this. This is gonna be an ORB and we're gonna queue and queue the pointer to the record box. And that also means that our help queue is gonna do operate on boxes rather than on records and ORB. And so what we're gonna end up with here, right, is this is gonna be a load of the, a load of the pointer that's in the box currently because it's gonna keep updating to point to new, basically think of it as like new versions off the operational record as it makes progress. So we're gonna load the latest version and look at it's completed status. So this is gonna be an unsafe dereference because the load of the atomic pointer is, oops, because the load of the atomic pointer just returns a raw pointer that we need to, or B dot val dot load.  And so safety here is gonna be, who knows yet, we don't have a safety guarantee for this yet because we don't know where these operational records are gonna come from. I think what's gonna end up with is in this stream or whenever we end up finishing up this part of it, it's just gonna be safety is we never drop anything. That's often a good sort of start approximation to get the algorithm correct and then do the memory reclamation. And then ultimately the safety argument here is gonna be because we're using hazard pointers, we know this pointer is still safe to access. But because we don't have hazard pointers yet, we can't actually get to that. All right, so let's see what they say. So the operational record and let's just sort of copy this whole sale for now, they say holds a owner TID, which is a thread identifier. I guess we could do, we have such a thing.  So help queue is gonna be generic over O and this is gonna be generic over O and the help queue is gonna hold the LF input. And we don't actually know what this is gonna hold yet. So we're just gonna say it holds an O for now. Where's the place where we construct one? All right, what other fields are there in our operation box? Input parameters for the operation. All right, so this here, you can actually see that this was done in Java because here they say there's the OP type and then there's the input parameters for the operation but in rest we have algebraic data types so these can combine into one thing, which is the input. So the input is gonna be O, which is the operation. So we could call it operation input, it doesn't really matter.  So state is gonna be operation state and here we might also be able to do some like, because we have algebraic data types, I guess this is gonna be owner, because we have algebraic data types maybe we can wrap up some other state in this. Result is operational result when completed so that's gonna be a part of this one. So completed is just gonna hold a T. So state is gonna hold and this is where it's a little weird, right? Because we have the input type and the output type. So for now, let's make them separate. I think in reality we can probably tie these together with some, with like a trait to say that the input type dictates the output type. I don't know if we necessarily need to, but let's start with having them be separate for now. So that means the help queue is also gonna be generic over the input type and the output type. So it's gonna take this and all of this is gonna be generic over both. So this is gonna be generic over the LF input and the LF output. Like so, what else, cast list. There's gonna be a list of cast descriptors. So castes, which is gonna be a cast descriptors. Oh, which is gonna have to be, it's almost like this should just be generic over LF, which is gonna be, I guess, a normalized lock free because then this can just be LF input. This can be operational state LF output and this can be LF castes. And now we can simplify this to say it's gonna be, oh, LF, which is gonna be a normalized lock free. This is gonna be generic over LF and this is gonna be generic over the LF. And so this is gonna be generic over the LF and so is this and so is this. And then that translates all the way up here and all the way up here. And this is probably gonna be a phantom data LF. So we're gonna bring in that just because we need to use the type, otherwise rust complaints. We actually need to say that we're gonna require that. All right. And now, right. So now it's gonna complain at us and say, when we initialate the operational record, we need to actually give the right fields. So let's go ahead and do that. And so the owner is gonna be a thread current.id. I don't know if the current thread ID is that relevant really here, but might as well give it.  Cause we don't know what they're gonna be as. I feel like there's something missing here, which is I think the castes are actually a property of the state. We'll see about that in a second. And then this is gonna be state.isCompleted or isFinal maybe, but completed. So we'll have a method on operational state, which is gonna be isCompletedTransibule, which is matches self, self-completed. Nice. Yes, we're gonna have to figure out what this castes should actually contain. Cause we don't actually have a castes here yet. I guess there's something missing here of like when the generator runs in relation to this, like how do we generate the list of castes? What does it mean that we're precasts? We're about to find out, I suppose. Cast list is definitely better than castes. Yeah, you're not wrong. In fact, maybe we should, yeah, we're gonna go through and clean up a bunch of these names after a while.  Both cast, cast is cast descriptor, cast descriptors. I'm not happy with input output. Contention measure is a little weird. I don't like this operation record box syntax, but I think for now let's just stick with what we have so far. I suppose we could at least make this cast list to sort of match the, ooh, what did I do? I did something weird to match the paper like I said we were gonna do. So what do they say? A cast descriptor, they say have a target, an expected value, a new value, and a state. So cast list is actually a list of these cast descriptors. Interesting, so this is not something they're generic over, which I think is kind of interesting. Like this doesn't include the stuff that gets passed to wrap up, for example. And maybe that's intentional. So maybe then, maybe operation record shouldn't be over LF. Maybe it should be over INO. Because it sounds like, it sounds like this should be a cast descriptor, but an actual concrete type. So like if we have a struct cast here that is target, which is gonna be a, oh right, this is why we did it, because there's not a, that's why. Yeah, so this really is an LF. This really is an LF. Ah, here's what we're gonna do. This really holds the cast type, but many of them. We don't actually want the full descriptor because the various metadata that might get generated to be passed to wrap up, we don't necessarily want to store here. I mean, maybe we just do, maybe it doesn't matter, but I feel like we might want to go a way like this. Let's see if we can pass the full castes and get away with it. Just to avoid defining our own array wrapping here. All right, it doesn't really say much about, like I'm imagining this cast descriptors is something that is then implemented by each user type. And you could totally imagine that we provide like a sort of standard implementation of cast descriptor for like atomic pointer casts, atomic U-sized casts, and then people just use those. Giving help. When a thread T starts executing a new operation, it first peaks at the head of the help queue. So we have that already. If it is a non-known value, then T helps the in queue to operation before executing its own operation. After helping to complete one operation, T proceeds to execute its own operation even if there are more help requests pending on the queue. All right, so this is the fast path thing that we already have, right? Which is if help help first. And it's a little unclear whether this should go inside the loop or outside the loop. Like if we retry, should we try to help again? It sort of sounds like we shouldn't. Like this should go here. But I'm not sure. To participate in helping an operation, a thread calls the help method. Is that what we called it? We called it help first, I guess. Telling it whether it is on the fast path and so willing to help a single operation or on the slow path, in which case it also provides a pointer to its own operation record box. In the latter case, the thread is willing to help all operations up to its own operation. The help method will peek at the head of the help queue and if it is a non-null operation record box, it will invoke the help op method. A null value means the help queue is empty and so no further help is needed. And then that's basically what we have encoded already. Although they're suggesting that the sort of pointer check in the while loop, whether it's a while loop or just a single help should go inside the help method. I don't think that matters. The help op and vote by the help method helps a specific operation O until it is completed. Its input is O's operation record box. The box may either be the current head in the help queue or it's an operation that has been completed and is no longer in the help queue. Right, so this is, you might peek, then the operation finishes and then you try to help the operation. As long as the operation is not complete. Okay, so let's actually write this method. So this is help op takes a self and a operation record box. And then what they're saying is, as long as the operation is not complete, help op calls one of the three methods as determined by the operation record. Okay, so we're gonna read the, so let's see, this is the ORB. So the OR is gonna be ORB.load. And this is another instance of the unsafety, right? Where we need to make sure that between, that basically that the target doesn't go away. This is where hazard pointers or something is gonna be necessary. Why, I'll write this is gonna be this. Load, this is gonna be dot val. So now depending on the state of the OR, we're gonna do different things. So if the operation state is completed, help op attempts to remove it from the queue using conditionally remove. When the help op method returns, it's guaranteed that the operation record box in its input represents a complete operation because no longer in the help queue. Okay, so if it's completed, then we're gonna do self.help.tryremovefront of the ORB. I don't think it actually cares about the return type. I don't think it cares whether it actually removed it or not. Ultimately, the promise of help op is that guarantees that when, that on return ORB is no longer in help queue. That's the guarantee it provides. So even if the tryremovefront fails, that just means that someone else must have removed it for us because the only thing that happens at the front are removes. So it's no longer at the front, then it's already been removed. Nice. All right, what else do we have? What happens to the value in here though? That's what I wanna try to find out. We don't take the value, which is kind of interesting. I guess, oh, I see what happens. So this could be executed by some thread that's not the one that's trying to return. And so even though we are the ones who remove it, like we observe that it's completed, we remove it from the head of the help queue, we don't deallocate the operation record box. Ultimately, the owner of the record box, remember, down here has this like while loop on when it's completed, they're done. And so here, I guess what they're gonna do down here is like extract value from completed and then return. In fact, this could probably be a loop with a match which should be pretty nice, which is like loop, well, it's gonna be a little bit of pain here, but basically the OR is gonna be this. And then we're gonna say if the operation state is completed, then we have some ref t here, then we're gonna return the t. You'll see why we can't do this in a second. Otherwise, we're gonna continue helping. And I guess this can be a break t. The problem here, of course, right, is that OR here is a reference to the operation record. And so we can't move the t out of the OR state. And then the problem here, of course, is we have no guarantee that no one else owns it. You could sort of think of this as like, if this is the case, we sort of wanna say let OR is box from raw of, I guess, OR, right? But this is unsafe. How do we guarantee that no one else holds a reference to the OR? So there's a memory reclamation problem here as well. I think what we'll do for now is actually just say that this is just gonna, we're just gonna leave it like this and then eventually the bar tracker's gonna yell at us, which is always a good problem to get into. Right, and then we have the other cases up here, which is if we're in any of the other states, right, we're gonna need to implement the other ones too. As long as the operation is not complete, help op calls one of the three methods as determined by the operational record, okay? The precast method invokes the cast generated method of the normalized lock free algorithm. Ooh, look, there's even a thing. You see, this is somewhat similar to what we have. We take advantage of the option type, so we don't have to do this null check. And you see, this is where they sort of encode this like loop if you're being helped and waiting for your operation to finish and don't loop if you're just on the fast path and being helpful. And I think we just want to not encode that and help function and instead have that be in the caller just either does a if let sum or a while. That's okay, I think. And so here, this is where this gets interesting. So help op reads out the value from the box to get the record. That's the same thing we do. Extract the state, and then I guess this is really just a match on the state which says, okay, if it's precast then it does precasts, which I guess is execute the generator and the OIC execute the generator and then stick the cast list into the state for like try to make it visible by basically sticking it into the operational record. So what we want here then is in here, what we're going to do is self.algorithm.generator of on here, I guess we're going to have to say, let actually, so this is interesting. We're going to do OR is OR.clone. Because we're going to do, we're going to sort of create our own copy of this operational record that we can then write back, right? This is the RCU part of things. And so we want to do, the generator is going to be given OR.input. And in fact, because we clone it, arguably we could just give it the owned version, but that's fine for now. The generator also takes contention. So this is where I wonder whether we should actually pass in the contention counter here. I feel like probably not. So maybe this is just like a contention measure that is not being used. What else do we have to pass the generator? Right, so this is going to be cast list. It's going to be this. And then what we're going to do is, I suppose, OR.castlist. So this is really OR.castlist is this. And then we're going to do OR.val.store. Or actually, this is going to be a compare and swap or compare exchange really of OR.castlist. Compare exchange week is fine, but let's do compare exchange. And it's going to change it from OR to, I guess this is going to be the sort of updated OR. And we're going to change it from OR to updated OR with a compare and swap because someone else might have helped this operation make progress anyway. And this is going to be, that's fine. And here actually, if we fail, that's fine. If this operation failed, we're just going to go round again. I think there's an argument for this actually counting as detected contention, but I think for now we're just going to ignore this. Like we don't care whether this succeeded or failed, but we are just sort of going to try. I guess actually there is one case here, which is if this is an error, then we can free the thing that we allocated. So this is sort of going to have to be a box new of this. That's going to be necessary. And then this is going to be updated OR. It's going to be box into raw because it's an atomic pointer. So we have to actually pass it raw pointers. And then if it failed, then we can do box from raw of the updated OR because it didn't get written anywhere. So it never got shared, so save to drop. Ooh, what did I do? I did something silly here. Oh, this is a box reference. That's not what I want. Operation record. Why does this clone the reference? That's really weird. Am I being silly?  Like that will do it. It's just weird. This should be a box operation record. Why does that not have a cast list? Oh. Imple clone for, because it doesn't implement clone is why. For LF where LF input implements clone, where LF output implements clone and where LF castes implements clone. We can't use derive here because that would require LF to implement clone. And that's not a thing we really want to require. So we're just gonna have to write it out. State.clone, operation state can totally be derive clone. It's a little sad that this thing is gonna end up being cloned a bunch. Even though it doesn't contain a T, you still need to do the mem copies. But I don't think that's terribly important. And cast list is gonna be self cast list clone. And this is gonna be normalized lock free. And so now what we're gonna require down here is we're actually gonna require where operation record of LF implements clone. And so now this doesn't need this anymore because it implements clone. That's great. And this is gonna be, I thought this should be automatically coerced, but I guess not. Why does this need to be mute? Operation record, operation record. Just to be explicit about the cast. Great. I actually don't like these annotations, but they sort of complicate this. Maybe I need to tune the coloring of them. But basically what we're doing is this is sort of the RCU part of this, right? Where we take the current operational record, we clone it, we run the generator, and we try to write back the result of running the generator so that another thread could sort of pick up and run from here. So we write, we try to sort of compare exchange back in the operational record, the updated operational record in place of the previous one. And if we fail, we just sort of, that's fine. We just sort of go around the loop and try to help more. And my guess is, and here we can free it because we never actually gave it out. If we succeeded, we can't free it, right? If we succeeded here with this cast, then now that operational record is given out. And it's the same thing here where if we succeeded the old one, we can't free because it might still be other threads. So this is another example of, we might need hazard pointers to actually free the operational records that do end up getting shared. We don't have a mechanism for. Isn't casting const to mute undefined behavior? No, const to mute is not undefined behavior. It's only going to a mutable reference that is undefined behavior. And we're not actually doing that here. In fact, it's unclear whether there's even a difference between star const and star mute at the moment. There's some debate about whether there should be, but there isn't currently. All right, so we did that. If it's execute cast, then failed index, I guess that just means try to execute the list. This to me, you know, this really looks like the cast list is a part of the execute cast and post cast states. So it's not a field. That's what I think. I think that execute cast here is a LF cases. I think this holds an LF cases and I think this holds an LF cases. That's what I think. And I think there's even an argument here for we don't need to clone it first. We can just construct a new one. So I think I don't want this to be cloned. Don't think I want this to be cloned. I think the operation state, I mean, this could just take, maybe this should just take LF actually, but let's say it takes LF output and LF, or LF casts an LF output like this. And so, yeah, so if we're in pre-cast, there is no existing list. In fact, I don't think we want to allocate this to that. I think we want to say let cast list is this, which is going to be OR.input. And then what we're going to do is we're going to say updated OR is going to be box new of operation record where the thread, the owner is just going to be OR.owner.clone. The state is going to be operation state execute cast of the cast list that we just generated. What else is even in there anymore? The input. I don't think the input is necessary either. I think the input is only relevant for the first step. So I think this is what holds the input. So at this point, this really just should hold LF. So in the pre-cast step, we hold on to the input. In the execute cast step, we hold on to LF casts and the post one we do as well, and here it's the output. That's much nicer. We're going to require that this is normalized lock free. That's fine. This is going to take LF. I think we don't even need this helper anymore. Now this can just be LF. Yeah, yeah, now we're talking. So the pre-cast is given the input and we can do input here. And then that just gets passed here to get the cast list. We're going to match on a reference to it because that makes it nicer. The input, then we allocate a new operational record where the state is the execute cast. And now there's no longer an input here. It's just the owner and the state. Yeah, owner and the state. And now we have our updated or. That's much, much nicer. And I guess let updated or is going to be box into raw so that we get a raw pointer that we can use with the cast. And now in the execute cast, we're given the cast list and I'm getting execute cast is going to be self algorithm or in fact, it's not even algorithm. Self cast execute of the cast list. And the, what else does the cast execute take? Contention, which we don't care about here. So this is going to be the sort of result of running the execute cast lists. And then it says record dot failed cast index is failed index and record dot state is both castes. So I think what that really means is same thing here. I think we're going to do is updated or is going to be this. This is just going to return because it doesn't need to do anything more. Now this is just going to be a nice state machine transition is what this is going to be now. Cause this stuff, all the stuff for actually updating the thing can happen regardless of what the actual type is. So we can hoist that out there. And now we can say here, we're also going to return an operational record where the owner is still going to be the same thing, but the state here is going to be post cast of the cast list. But dot clone, I suppose. And I think the result has to be passed along here too. So I think post cast also needs to take a result of nothing and use size, which is the outcome of the cast execute, right? Yeah. And then post cast is given the cast list and the outcome, if you will. So let's call this outcome. And what's it going to do with the outcome? It's going to do post castes, which I think is just the wrap up, right? Algorithm dot wrap up, which is supposed to be given the outcome and the cast list and a contention counter, which we don't care about. And what is it supposed to do? That is the, we're going to match. We're going to just wrap up return or returns the result of self output and nothing. So we're going to have the, in fact, what's that even going to do? Is that even going to update it? So this one is actually not going to update. This is going to be if let okay result is wrap up, then we're going to return a box new operation record, right? So this is transitioning the state machine forward to the completed state with the result. Or in the else case, we need to restart from the generator, which I guess means we're going to just do, we're back at precasts, which needs the input. So that's the reason why the input should actually be a part of the whole thing rather than a property of precasts. But that's fine. Okay. So basically what we have here is like, we're moving our way through a state machine, but all of the threads are trying to move through the state machine sort of cooperatively. Interesting. Okay. And we need to find a, the input is going to have to be carried along with each one. So precast is nothing and input is LF input, because that needs to be preserved even across the sort of retry loop, which means this will actually be OR.input and input will be OR.input.clone. So we're actually going to end up requiring that input is cloned and castes is cloned. Yeah. And so we're going to bring the input forward in each new box. And now precast here is just this. Okay. So now we have the state machine for actually driving helping forward. I know I talked a lot without pausing there, but the rough architecture makes sense and how this sort of ends up having everyone cooperate on the sort of state machine for execution. Five D chess. Nice. And we still have this guarantee that on return, orb is no longer in the help queue. We're going to have that guarantee by saying that this all happens in a loop, right? So we're going to keep looping until we observe that it's in the completed state. And once it's in the completed state, then we just return. Yeah. It definitely needs to not be called castes. It's bad. I guess cast list is right. The reason cast list is a little misleading in this context is because it really is a descriptor for all the castes, right? Cause it's a list of castes and also information for wrap up. So it's not strictly speaking just a cast list. It's like computational state or something. Three hours ago, we were all like, seeing the code will help. There's help. Isn't it better when there's code? Yeah. All right. So I guess what we want then is this now is just self help op of help. Nice. Which really means, this is another instance of hazard pointers will help us here. We hope. And I'm going to remove these safety comments to try to deal with them separately. And now there's no cast list here. So that one is done. This. Yeah. And now we finally get to the bar checker error, which tells us can't move the T out of the AOR.state. There are a couple of ways we can deal with this. What we're going to currently do is we could require the result to also be clone. I think ideally we wouldn't have to do that, but let's just, we're going to start easy and go the Java way and say we're going to break with t.clone. Oh, public interface. Why is it in the public interface? Private type operation record in public interface. I disagree. Ah, the help queue is not public. Try that on for size. And of course, we haven't actually implemented the help queue. So none of this will actually work yet. Why does it now complain about private type operation record in, oh, this isn't required anymore. So that's fine. What other warnings you got from me compiler? Huh? Bring it on. Bring it on. I can deal with your warnings. Atomic bool not used. Boom, get rid of it. Atomic use is not, boom, done. Two warnings. Help not used. That's right. It's a to do. Can I pass things to to do? Fine. No warnings. It compiles. Ship it. It's done, right? Yeah, I mean, it would be nice to pass things to to do, but this is fine. Like, it's going to be pretty obvious to us when we try to implement this that we're going to need this value and same with this. So I'm not too concerned about that. Ooh. All right. I think, so we're now at the, what, four and a half hour mark? I think we're probably going to end it there. So the reason I say that is because the, oh, there's a monitor run business here. This is probably related to pre-casses. Does post-casses have the same?  So there's definitely stuff we're missing here in the state machine. So we're going to, so this is going to have, all right, so let's try to do a little bit of what it's telling us to do here. So it says pre-casses a monitored run. What is a monitored run? It runs a monitored version of the generator which occasionally checks the contention failure counter in order to guarantee this method will not run forever. All right, interesting. Interesting. If cast list is not no. I see. So what they're saying here really is that the, oh, that's weird. Basically what we're saying here, right, is that the generator is supposed to sort of give up if it does, if it runs for too long. Interesting. And then the cast is not equal to null here is if the generator runs for too long, like if it encounters too much contention, it's gonna, instead of generating a list of castes, it's actually going to return and go, I can't do it. So think of it as more as it returns an error. And in that case, pre-casses actually doesn't do anything else and just sort of loops. So I think what we do here is, how are we gonna have a monitored version of the generator? That's what I wanna know. Which occasionally checks the contention failure counter in order to guarantee this method will not run forever. You know, here's a cool way we can do this. Well, let's say this is probably the last thing we do, which is detected is gonna return a result of nothing and contention. We're gonna have a struct contention that doesn't have any fields. And if self.zero is greater than the contention threshold, then we're gonna return, then if it's less than the threshold, we're fine. Otherwise, we're gonna return a contention error. The reason we do this is because now, what we can say is that if you're writing an implementation of generator, this is actually gonna return result of either a castes or a contention. So you can use the question mark operator in generator on contention, like on the contention object.detected. And it will make sure to return early if you've detected too much contention. That's really cool. And so now what we can do down here is say that we can match on this and say that if this is okay, cast list, then it's a cast list. If it's an error of contention, then that we do the same thing they do, which is sort of have precasts do nothing and precasts doing nothing just means we loop. So we loop. So this is basically a monitored run. That's really neat. And then I'm guessing for cast execute, there's something similar. And for post execute, there's something similar. So let's see if we find post castes.  So wrap up has the same property of, ooh, what should that even do? So wrap up can already fail because the overall operation hasn't completed and should be retried, but can also fail because of contention. And I guess that's fine. I think we can wrap those both up and say, those are both because of contention. And so we're gonna do in the post cast case is operation result is null. That's equivalent to the monitored run fail. There was contention. Oh, okay, so it's a little different whether wrap up encountered contention and wrap up wrapped up without contention, but the operation didn't succeed. And those are different things. So we do actually need to represent these different. So maybe this is option contention, right? So either wrap up succeeds or it fails in which case maybe there's contention. And I think question marks should still work there. So definitely a little weird of a return type, but I think it's warranted here. So what we do is we match on this and we say if it's okay result, then this, if it is error with no contention, then we need to restart the generator. And if it exited because there was contention, then basically it's not our job, not up to us to restart. And so we continue. All right, so that's the, if operation result is null, that is like we didn't succeed in wrapping up because there was contention. And so therefore we return, which is the same as going around the loop again. Otherwise we need to figure out whether we should restart and should restart is the same as the, us not complete. I guess, okay, maybe the better way to represent this is actually that it's a result option output. I think that's the better way to say this, that it errors if there's contention because wrap up didn't finish or it succeeded with wrap up, but there's no result. That's a nicer way to do it. So either we got an okay some result or we got an okay no result or we got a contention error in which case it's not up to us to restart because we didn't even finish our wrap up. Nice. And I think that is, it says, okay, so we keep the owner tid, we keep the operation, keep the input. If we're restarting, then we set it to pre-casses. So we're post-casse. If it's restarting, we set it to pre-casse and null-null doesn't matter because we have algebraic data types. If it's not should restart and operational result is not null, that's the same as an okay some. And so then we keep all the same, we set it to completed and we include the operational result, which is what we do here. We just stick the result in completed. And in either case, we do a compare and set of value for record. So that's the same thing as up in pre-casse where we try to update the thing. But notice that we don't actually check the return value because it doesn't matter. Nice. And in fact, I think this swap down here can be comparison week. We don't want it to retry it equally. And that's okay. Nice. So really execute cast, I think is the one that still needs a bit more fidelity because our implementation of cast execute is probably not quite right. Right, so we see it's like, we loop through the descriptors. Oh, and it checks whether the cast has already succeeded because some other thread may have completed it. So there's some stuff missing here, which is like check if already completed. So really this is really like implement figure five. But that looks like it's a little bit more involved. And I don't think I want to get into that today. I think we'll probably do like the execute cast and all of the weight-free queue stuff later on, like in some subsequent stream. I think that's a good place for us to stop. I think this is sort of, we're now at the heart of execution and that's where we're gonna need the weight-free queue anyway and we're not realistically gonna do that today. So I think that's where we're gonna stop. At the tail end though, let's see if there are questions on like, how do we get here? Where's the current state of affairs? Anything weird about what we've done so far? And also thoughts about what you wanna see next. So I think there are two paths to go down. Like ultimately we're gonna have to do both of them to actually get to a finished implementation. But our options are either next stream, finish up the cast execute, the weight-free queue and sort of tying it all together or next time we do hazard pointers and memory reclamation. Like ultimately they're both needed but we can do them in either order. So thoughts on that would be interesting too. Yeah, the algorithm translates really nicely into Rust. Part of that is just because it's a state machine and state machines are really great with algebraic enums. Yeah, the cat wants attention. She's gonna eat J. Can you explain why the weak cast failing sprucily is in the problem? The swap down here, it doesn't matter if the compare exchange fails because we're gonna loop anyway. In general you only want compare exchange non-weak if spurious failures won't be retried but they need to be. In this case they will be retried in the form that will run the loop again. So it seems like it's like 50-50. Some people are like hazard pointers sound cool and the other half is like let's deal with memory reclamation later. Let's just get to working. Yeah, I'll do some thinking and see where I go next. I honestly don't know yet. They're both gonna be really interesting. The hazard pointer just like, just nerd snipe me really hard and I started looking out the code and I was like, that's very exciting. All right, well, I think we're gonna end the stream there then. I don't know when I'll end up doing the next stream. These are fairly long. I'm guessing in like two weeks, two, three weeks is my hope, but we'll see. Thank you all for coming out. Thank you for watching if you're watching this in video demand afterwards and I'll see you next time. So long for well. Bye.


Hey there lugs and ladies it is Petrifying Pumpkins here, and I have got a quick Impressions video for you today. That is because I have played Alvo the closed base a Long long last I have finally gotten my hands on the game I've gotten to play it for about an hour or so. I played it so far, but I just wanted to give some quick thoughts on My early impressions. I suppose I should say about the game Before I do so I've got disclaimers Disclaimer number one. I am a community mod racer for first contact entertainment first contact entertainment, of course Made firewall zero hour and someone consider Alvo a direct competition with firewall So if you think there's a conflict of interest there or whatever, that's fine You can take everything I'm saying with the pinch of salt. All I will say is that I'm going to speak Honestly about my thoughts on Alvo, but if you don't want to believe that that's that's fine, right? Second disclaimer is that because I'm playing in a closed beta There is just a bunch of NDAs a lot of stuff. I can't talk about it So I have to be very careful what one I'm saying in these early impressions So I guess the first thing maybe to say is Thank you first of all to Steve at Merton poll for getting me into the base And let me try out his game Secondly, if you're just gonna ask me right off the bat is Alvo good based on what I've played I would say yes game obviously we've been following on this channel for quite some time It has had a very rocky development gone through Cancelation it's been brought back from the dead So I definitely had a lot of concerns for Alvo in terms of is this going to be you know Just a disaster or is it going to be Quite a lot of fun as it turns out. It seems to be quite a lot of fun There is issues. There is a lot of stuff that needs to be dealt with but once again Early stages of a closed base. I was talking to Steve himself when I was playing the Alvo and he was pointing out Oh this thing here. We know about this We're it's gonna be fixed before launch or you know this level that you're playing right now There's things that you know, this is an older version of the level that We couldn't get updates it into the base it for whatever reason but when the game officially launches It's gonna have the updated version and this is gonna be better And this will be fixed and that will be fixed and if everything that he said turns out to be the case Then I think Alvo is definitely something you should have on your radar now another question I expect I guess a loss is because well because of this channel's close ties to firewall zero hour is Is this game better than firewall zero hour or how does it compare to firewall zero hour? And I will say the same thing basically that I've been saying You know just by looking at the videos available that these two games are different things I know they're both virtual reality first-person shooters, but that's kind of where the similarities and I've said this a lot of times you probably sick of me saying that firewall is the rainbow six siege approach very kind of slow-paced Tactical, you know one life your house, so maybe increase tension whereas Alvo is the call of juicy arcadey Fast-paced you're in you're dead. You're in you're dead. You know, it's got killstreaks all that kind of stuff And in if the same thing we've learned it's that there's room for both of those kind of games on any platform So the fact that the ps4 is getting both of these kind of games now these kind of genres covered I think is good. So I did have a few questions for Steve that I asked and he kindly answered on the discord So the first question I asked him was there was there going to be a difference between the PlayStation 4 pro version of the game And the standard PlayStation version. He said that there was no pro patch So both versions of the game are going to be the same the second thing I asked him was is this closed BASA going to go to open BASA and then full release or is it going to go from this closed BASA and Then transition to a full release and he told me it's going to go to a full release So there won't be an open BASA. They are expanding the slots of how many people are in the closed BASA right now I believe it's a pretty small number, but that they can expand it up to 1000 I believe that's what he's meant publicly available or that's public info on the discord server So about a thousand people can get in but there won't be an open BASA And lastly I asked him if there was going to be plans to have a party slash you know squad and up feature because as it is right now you kind of just boot up the game Hit search for the whatever mode you're playing right now in the closed BASA. It is just free for all So you pick that mode and then it just instantly loads you into a session and There's either players in there or if there's gaps Then it fills that those gaps with bots and from the reply I got from Steve It seems that the party system is being worked on and they're hoping to have it in by launch So fingers crossed that is the case because of course these games are always fun to play with friends Now there are our plans for different modes search and destroy Custom modes and stuff like that as well so you can have a lot of fun that way So as I said already, I didn't play too much of the game. I played for about an hour I was playing with Steve and some other people from my sumo was Mardin bold I wanted to jump straight into us. I didn't mess around too much with the customization The weapon selection attachments that kind of stuff. Well, I went through the menus, but a lot of stuff was locked I hadn't I had to earn the currencies or whatever to unlock the sites and all that So I played with the AK-47 played with a handgun Grenades and I like I pretty much just use the default loadout number one I think it was and it looks like you can customize up to four loadouts and put whatever you want in them loadouts So that you can quickly switch between them whenever you want because when you die It's actually really cool. You can just hit the square button and pick a loadout Which is again reminiscent of college UC. So another likeness there to college UC I want to talk a little bit about the maps. I guess what I'm saying too much from what I saw There was four maps available in the rotation now There is variations of those like nighttime tape time and maybe dusk as well I'm not too sure about that, but I thought the maps were quite enjoyable. They're quite spread out At least the desert map was really kind of open and there was lots of verticality You could climb on top of buildings and it was sniping and all that kind of cool Chat and then there was the monastery that felt a little bit more claustrophobic with ten of us running around or however many people were there at the time There was the house which I think I only played it at night time and it was kind of very it was a nice map I think maybe house so far might be One I prefer the most now. That's just after an hour playing with just one you've gone So there's lots of variations and like different weapons might suit different mouths better and all that kind of stuff And then of course there was the town map which was also a lot of fun That's I think that's the very first one. They showed off with that initial reveal trailer all the way back in like 2017 I think at this stage and so yeah I mean there's a lot of stuff I want to say a lot of more things I'd like to talk about but I have to be careful that I'm not violating NDA's and stuff like that. So these are while they're my first impressions. They're not my full first impressions Hopefully as we get closer throughout the base up those ease up or you know when it comes to release I'll do proper Impressions video, then I guess when the game is closer to what it will be when it launches Anyway, that is it for this video as in ladies. Thank you very much for watching before I go Let me thank my patreon supporters whose names are on the screen as I speak Thanks, they're generosity. They're keeping this channel going nice and moist in particular Let me give a huge shout out to the following crumb Pete Hawkins chopped 517 Tradition and Daniel the pumpkin patch kid. Thank you very much for that generosity really do appreciate us If you too would like to help on the patreon the link to that will be in the description below But if you don't want to do that, you can just do the you know the usual likes Subscribing comments usual YouTube shies and I'd appreciate that very much as well Finally, let me thank Decepticon for let me use his music in all these videos. Thank you very much Decepticon You can find him Decepticon calm link in the description below and with that I will end this video Thanks very much for watching stay moist