C
C#•10mo ago
Rome

Handling events in "blocks"

Hi, I am dealing with a problem where I need to move an Elevator based on the inputs received. The inputs are received by a comma delimited string. For example: 9,8,2,d3,7,4,3 The d3 indicates that the input is delayed by 3. The event for 9, 8, and 2 will all fire in a sequence. After the Elevator was moved 3 times the events for 7, 4, and 3 will fire. Basically 9, 8, 2 are pressed "at the same time" and 7, 4, 3 are pressed at the same time after the Elevator was moved 3 times. The issue is that when the 9 event occurs, the Elevator has started moving towards 9 and that by the time event 2 fires the Elevator has already passed floor 2. The Elevator does end up going to 2 eventually on its way back down but I need it to go to 2 on the way up since 2 should have been received together with the first block of 9, 8, 2. How can I wait for the first block of events to occur before moving the Elevator?
private void OnButtonPushed(object sender, ElevatorControllerEventArgs e)
{
requestsQueue.AddLast(e.Floor);
SetDirection();
}

private void MoveElevator()
{
while (requestsQueue.Count > 0)
{
if (requestsQueue.Contains(currentFloor))
{
requestsQueue.Remove(currentFloor);
controller.Stop();
}
else if (isMovingUp)
{
currentFloor++;
controller.MoveUp();
}
else
{
currentFloor--;
controller.MoveDown();
}

SetDirection();
}
}

private void SetDirection()
{
LinkedList<int> requestsGoingUp = GetAllRequestsAboveCurrentFloor(requestsQueue, currentFloor);
LinkedList<int> requestsGoingDown = GetAllRequestsBelowCurrentFloor(requestsQueue, currentFloor);
if (currentFloor == topFloor || requestsGoingUp.Count == 0)
{
isMovingUp = false;
}
else if (currentFloor == 1 || requestsGoingDown.Count == 0)
{
isMovingUp = true;
}
MoveElevator();
}
private void OnButtonPushed(object sender, ElevatorControllerEventArgs e)
{
requestsQueue.AddLast(e.Floor);
SetDirection();
}

private void MoveElevator()
{
while (requestsQueue.Count > 0)
{
if (requestsQueue.Contains(currentFloor))
{
requestsQueue.Remove(currentFloor);
controller.Stop();
}
else if (isMovingUp)
{
currentFloor++;
controller.MoveUp();
}
else
{
currentFloor--;
controller.MoveDown();
}

SetDirection();
}
}

private void SetDirection()
{
LinkedList<int> requestsGoingUp = GetAllRequestsAboveCurrentFloor(requestsQueue, currentFloor);
LinkedList<int> requestsGoingDown = GetAllRequestsBelowCurrentFloor(requestsQueue, currentFloor);
if (currentFloor == topFloor || requestsGoingUp.Count == 0)
{
isMovingUp = false;
}
else if (currentFloor == 1 || requestsGoingDown.Count == 0)
{
isMovingUp = true;
}
MoveElevator();
}
40 Replies
Pobiega
Pobiega•10mo ago
You'll need to process all events until a delay/end of sequence before you start moving In your example above 9 8 and 2 are all happening at the exact same time, right? Ie, the elevator should know where it needs to stop before it starts moving. An ordered queue, of sorts
Rome
RomeOP•10mo ago
Yes the elevator knows when to stop. The events for 9,8,2 happen at the same time in theory but the controllers fires them sequentially. Unfortunately I cannot edit the controller.
Pobiega
Pobiega•10mo ago
what is "the controller"? by your snippet, it seems you have all the control of it yourself in the MoveElevator method
Rome
RomeOP•10mo ago
The controller processes the events in order of the sequence order. Here is the part that's relevant to that process. My example sequence above would look something like i9,o8,o2,d3,i7,i4,o3. I removed the i's and o's because they're not relevant to the order, however the d's are.
private void ProcessEvent(string ev)
{
if(ev.StartsWith("O"))
{
int floor = 0;
if(Int32.TryParse(ev.Substring(1), out floor) && null != OutsideButtonPushed)
{
OutsideButtonPushed(this, new ElevatorControllerEventArgs(floor));
}
}
else if(ev.StartsWith("I"))
{
int floor = 0;
if(Int32.TryParse(ev.Substring(1), out floor) && null != InsideButtonPushed)
{
InsideButtonPushed(this, new ElevatorControllerEventArgs(floor));
}
}
}
private void ProcessEvent(string ev)
{
if(ev.StartsWith("O"))
{
int floor = 0;
if(Int32.TryParse(ev.Substring(1), out floor) && null != OutsideButtonPushed)
{
OutsideButtonPushed(this, new ElevatorControllerEventArgs(floor));
}
}
else if(ev.StartsWith("I"))
{
int floor = 0;
if(Int32.TryParse(ev.Substring(1), out floor) && null != InsideButtonPushed)
{
InsideButtonPushed(this, new ElevatorControllerEventArgs(floor));
}
}
}
ProcessEvent() is called in proper order of the sequence i9,o8,o2,d3,i7,i4,o3. The sequence is stored in a dictionary that'll look like { {0: [i9,o8,o2], 3: [i7,i4,o3] } } . A for loop goes through the sequence, here's the part that does that.
for(int pastIndex = prevStart; pastIndex < sequenceIndex; pastIndex++)
{
if(sequencedEvents.ContainsKey(pastIndex))
{
List<string> eventsAtIndex = sequencedEvents[pastIndex];
if(eventsAtIndex.Count > 0)
{
prevStart = pastIndex + 1;
foreach(string ev in eventsAtIndex)
{
ProcessEvent(ev);
}
}
eventsAtIndex.Clear();
}
}
for(int pastIndex = prevStart; pastIndex < sequenceIndex; pastIndex++)
{
if(sequencedEvents.ContainsKey(pastIndex))
{
List<string> eventsAtIndex = sequencedEvents[pastIndex];
if(eventsAtIndex.Count > 0)
{
prevStart = pastIndex + 1;
foreach(string ev in eventsAtIndex)
{
ProcessEvent(ev);
}
}
eventsAtIndex.Clear();
}
}
Pobiega
Pobiega•10mo ago
great, and all this code is set in stone, just to clarify?
Rome
RomeOP•10mo ago
At index 0 it'll process 9,8,2 index 1 - nothing index 2 - nothing index 3 - it'll process 7,4,3 correct, yes
Pobiega
Pobiega•10mo ago
great
Rome
RomeOP•10mo ago
I was googling around and was thinking a delay could work or a ManualResetEvent but I was unsure as where to place it. It could be a valid solution as I have not ruled it out.
Pobiega
Pobiega•10mo ago
I'm not entirely following your logic in regards to the delay
Rome
RomeOP•10mo ago
Trust me, me neither LOL. My thought process was to delay before calling MoveElevator but I had no idea what I was doing so I scrapped the idea but it could work?
Pobiega
Pobiega•10mo ago
nah so the problem is that your MoveElevator blocks once you call it, it starts moving until its request list is empty with no chance of adding anything new to that list while its working so when someone presses 9, it moves to 9 before seeing 8 considering your desired result of it stopping at 2 when 9 8 2 is pressed at index 0, I'm leaning towards thinking the elevator only moves when the index increases...
Rome
RomeOP•10mo ago
yes and no. It starts moving to 9 but somewhere between 1 and 9 the event for 8 occurs and the queue is updated accordingly. It will stop at 8. The problem is that it should've stopped at 2 as well.
Pobiega
Pobiega•10mo ago
that makes no sense with while (requestsQueue.Count > 0) that loop will not stop until its reached its destination
Rome
RomeOP•10mo ago
Sure, I did not paste the in between steps where the queue is updated as I did not think the logic for that was important to the problem but I updated the original post.
Pobiega
Pobiega•10mo ago
is the button press events all the information you get from the controller? im not entirely sure how your elevator is supposed to... "respect time" so to speak meaning, how much is it allowed to move "per turn"? does calling controller.MoveUp() advance the timer?
Rome
RomeOP•10mo ago
Pretty much, yes. The controller returns the destination floor when the event happens (e.g. 9 8 2). The methods to Stop, MoveUp, and MoveDown are also exposed but that doesn't affect the event order in any way.
Pobiega
Pobiega•10mo ago
ok so for the 9 8 2 d3 7 4 3 scenario... when do we see the 7? I'm sorta thinking 9 8 2 app happen in one "frame" of time, then the elevator moves to 2 and starts heading towards 8 before 3 "units of time" have expired (one per MoveUp? One on Stop too?) and sees 7 before it reaches 8 so it heads to 7, 8 then 9 (because its moving up), then goes back down to 4 and 3
Rome
RomeOP•10mo ago
that's exactly how it's supposed to go
Pobiega
Pobiega•10mo ago
okay, then I think I've figured it out 😄
Rome
RomeOP•10mo ago
each MoveUp, MoveDown, and Stop is 1 unit of time
Pobiega
Pobiega•10mo ago
perrrfect The problem we need to solve is that any new button press must be able to stop the current move.. what kinda app is this? forms?
Rome
RomeOP•10mo ago
just a console app
Pobiega
Pobiega•10mo ago
okay can you post the top level code that "runs" this entire thing? I'm guessing it looks like
var ec = new ElevatorController();
var elevator = new Elevator(ec);

ec.Run("i9,o8,o2,d3,i7,i4,o3");
var ec = new ElevatorController();
var elevator = new Elevator(ec);

ec.Run("i9,o8,o2,d3,i7,i4,o3");
or something along those lines in fact, can you paste all the code you currently have for this at $paste ?
MODiX
MODiX•10mo ago
If your code is too long, you can post to https://paste.mod.gg/ and copy the link into chat for others to see your shared code!
Rome
RomeOP•10mo ago
Oh that's perfect. I was getting annoyed by the character limit lol. Here's what I have so far. The only part that's stopping me from meeting the requirements is processing each "block" of inputs together. The logic file is the only thing that can be edited. https://paste.mod.gg/lljkatnacblm/0
BlazeBin - lljkatnacblm
A tool for sharing your source code with the world!
Rome
RomeOP•10mo ago
I have to leave for a few hours. I'll try to think about the problem while I'm gone but if you do find the solution please don't give it to me right away. Maybe just give a hint to point me in the proper direction 🙂 ... thanks for your help!
Pobiega
Pobiega•10mo ago
sure 🙂 I need to put the kids to bed, so I'll be a while too
if you do find the solution please don't give it to me right away
Love that attitude. Keep that shit up and you'll do great. I've looked at it for a bit and I'm still unsure about what the expected end result should be... The time aspect is still confusing - since the button push event (or the controller) doesn't expose any time information, we can never tell the time (or even current floor, wtf) unless we track it ourselves. The execution flow gets really wacky with the controller calling our handler, calling back into the controller that calls back into the event handler etc we cant do a while loop until our list of targets is empty, as we might get new inputs as that is running... I wonder if we should let the first event handler start a method invocation and tell the others not to, somehow... I've reached this point with my code now: [1]<2 [2] <3<4 [4] <5<6<7 [7] <8 [8] <9 [9] >8>7>6>5>4>3 [3] Its not entirely perfect thou.. Essentially, we start at 1 and once 9 is pressed, we immediately start moving up. Then 8 and 2 are both pressed (elevator at floor 2). We stop at 2, then keep going up. When we are at 4, 4 is pressed so we stop. Then keep going up to 7, 8, 9. At that point, only 3 remains in our queue and we go down to 3. Then the queue is empty and no other button presses are coming in, so we stop. It seems to work fine, but I don't like the fact that we move to floor 2 before seeing those presses, but at the same time I don't see how we avoid that The "trick" was to think about how an elevator behaves differently if its currently moving or not when a button is pressed. If you need a more detailed hint, lmk
Rome
RomeOP•10mo ago
I believe that's the correct output for i9,o8,o2,d3,i7,i4,o3. Yeah, you're correct about the elevator being at floor 2 when the events for 8, 2 happen. It should still be on floor 1 but I also see no way around. The only workaround I see is to pass the input sequence to the Logic class so it knows how many "groups" of inputs are coming in but unfortunately that's against the requirements. I have an idea of keeping track of the units of time that's passed for each move (Up, Down, Stop) and somehow use that to keep track of each grouping but I haven't quite figured out how I'll do that lol. How are you getting it to stop at floor 2 for 8, 2? Did you switch the structure of the loop or add if statements in the while loop?
Pobiega
Pobiega•10mo ago
How familiar are you with the idea of the stack (as in stacktrace) and stackframes? ie, what happens when a method calls another method
Rome
RomeOP•10mo ago
Not familiar at all
Pobiega
Pobiega•10mo ago
No description
Pobiega
Pobiega•10mo ago
the idea is that if method a calls method b which calls method c, method b and a are still both "active" until method c returns and since the only way that the controller calls your logic is via OnButtonPushed, there were issues ie, ButtonPushed(9) was still active (as frame 1) when ButtonPushed(8) (as frame 2) and ButtonPushed(2) (as frame 3) were called I ended up making it so that I never had more than 2 frames of OnButtonPushed` active at a time - by having the "outer" frame be active until the entire queue was empty the "inner" frame simply added a new destination to the queue, then it returned so the "outer" frame was what did all the actual movement I can post my actual code for this if you want, but it will more or less spoil the "hard" part of this puzzle
Rome
RomeOP•10mo ago
I think you might have to post your code since I don't understand it at all lol. I'll have to read your code to really understand your approach. Do you think an approach using EventHandlerList is viable? I was reading this documentation and thought it might be promising https://learn.microsoft.com/en-us/dotnet/standard/events/how-to-handle-multiple-events-using-event-properties
How to: Handle Multiple Events Using Event Properties - .NET
Learn how to handle many events by using event properties. Define delegate collections, event keys, & event properties. Implement add & remove accessor methods.
Pobiega
Pobiega•10mo ago
I don't see how that helps at all tbh its just a way to have multiple handlers, which we already have. EventHandler<T> is a multicast delegate here is my event code:
private void OnButtonPushed(object? sender, ElevatorControllerEventArgs e)
{
AddNewTarget(e.Floor);

if (_running)
{
return;
}

_running = true;
while (_up.Count > 0 || _down.Count > 0)
{
UpdateDirection();
Move();
}

_running = false;
}
private void OnButtonPushed(object? sender, ElevatorControllerEventArgs e)
{
AddNewTarget(e.Floor);

if (_running)
{
return;
}

_running = true;
while (_up.Count > 0 || _down.Count > 0)
{
UpdateDirection();
Move();
}

_running = false;
}
I have separate lists for up/down destinations, which is probably a bad idea 😄 my total code for this is very nasty and I'd like to heavily refactor it so you can see that whatever happens, we always register the new target. then, we check if the elevator is currently "running" if it is, we stop. if its not, we set it to true, then initiate the while loop finally, after the while, we set running to false (so we dont end up in a bad state)
Rome
RomeOP•10mo ago
Did you change how it processes the queue? I'm not fully understanding it but I like the idea of checking if the elevator is running. I'll explore that some more and see what I can come up with.
Pobiega
Pobiega•10mo ago
nope, I did not touch the controller
Rome
RomeOP•10mo ago
No, I mean the MoveElevator() method in the Logic class.
Pobiega
Pobiega•10mo ago
Oh, yeah I rewrote the logic class from scratch but tbh what each method does should be self-explanantory update direction figures out if its time to change direction or not, and does it if needed. (that usually jsut means if the current list is empty, or if we have no known direction) move just calls the correct MoveUp/MoveDown/Stop command see, the crux of the problem here is the relationship between controller and logic and their contact surface the controller initiates the entire thing with ExecuteSequence, which will push the first button. In response to this, we can either do something or nothing. If we do nothing, the next button gets pushed (since 9 8 2 are the first three buttons at time 0) but we dont know that Logic doesnt know that there are three buttons, it doesnt have access to the concept of time so when do we enter a loop? the only thing that makes sense is at the start, because we cant possibly know if there will be another button press or not I'm gonna go sleep now, but good luck. If you need more hints, let me know and I'll deliver in ~6-8 hours 🙂
Rome
RomeOP•10mo ago
Thank you for your help and good night 🙂 ... I'll do some more work on this and will get back to you once I have the solution @Pobiega So, I figured out that the problem was that we kept starting a new loop everytime we received a button pressed event. We should have only started the loop on the first event and just add to the requests on subsequent event. Here is the updated event handler:
private void OnButtonPushed(object sender, ElevatorControllerEventArgs e)
{
eventsReceived++;
// start elevator on first event received
if (eventsReceived == 1)
{
requestsQueue.AddLast(e.Floor);
MoveElevator();
}
// else just add floor to request to avoid multiple loops running
else
{
requestsQueue.AddLast(e.Floor);
}
}
private void OnButtonPushed(object sender, ElevatorControllerEventArgs e)
{
eventsReceived++;
// start elevator on first event received
if (eventsReceived == 1)
{
requestsQueue.AddLast(e.Floor);
MoveElevator();
}
// else just add floor to request to avoid multiple loops running
else
{
requestsQueue.AddLast(e.Floor);
}
}
Pobiega
Pobiega•10mo ago
That's exactly what my implementation did
Want results from more Discord servers?
Add your server