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?
40 Replies
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
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.
what is "the controller"?
by your snippet, it seems you have all the control of it yourself in the
MoveElevator
methodThe 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.
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.
great, and all this code is set in stone, just to clarify?
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
great
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.
I'm not entirely following your logic in regards to the delay
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?
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...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.
that makes no sense with
while (requestsQueue.Count > 0)
that loop will not stop until its reached its destinationSure, 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.
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?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.
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 3that's exactly how it's supposed to go
okay, then I think I've figured it out 😄
each MoveUp, MoveDown, and Stop is 1 unit of time
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?
just a console app
okay
can you post the top level code that "runs" this entire thing?
I'm guessing it looks like
or something along those lines
in fact, can you paste all the code you currently have for this at $paste ?
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!
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!
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!
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 awayLove 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, lmkI 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?How familiar are you with the idea of the stack (as in stacktrace) and stackframes?
ie, what happens when a method calls another method
Not familiar at all
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 puzzleI 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.
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:
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)
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.
nope, I did not touch the controller
No, I mean the MoveElevator() method in the Logic class.
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 🙂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:
That's exactly what my implementation did