NorviewFIRSTRobotics / FRCStrongHold2016

http://www.norviewrobotics.org/
1 stars 0 forks source link

Thinking about how to implement the details of an activity #12

Closed Seairth closed 8 years ago

Seairth commented 8 years ago

Let's take the BreachSimpleDefense activity and see how we might accomplish it. Here is one possible approach:

  1. Move until FWD sensors detect both shields
  2. Move until AFT sensors detect both shields
  3. Move until FWD sensors detect both shields
  4. Move until AFT sensors detect both shields
  5. Move for 2 seconds
  6. Stop

Suppose we were to define each of the steps above as their own activity:

  1. ApproachDefense
  2. EnterDefense
  3. ExitDefense
  4. ClearDefense
  5. MoveForward

Now, we don't really want to change Robot.currentActivity because we would then have to keep track of nested activity levels in the robot (i.e. push the current activity on a stack, pop it back off again later). So... how can we execute these simple activities and still keep track of the fact that our top-level activity is BreachSimpleDefense?

One way is to make BreachSimpleDefense just like Robot, but implementing the IRobotActivity interface! Now, each time that BreachSimpleDefense.update is called (via Robot.currentActivity.update()), we turn around and call update() on its own currentActivity member! Note, however, that this also requires a "default" activity that encapsulates the steps above (just like the autonomy activity provides for the robot).

Now, having to implement the "default" activity (both at this level and at the Robot level) might be cumbersome. So, suppose we were to change the IRobotActivity interface slightly by adding a method:

bool isComplete()

With this, we can still allow some activities (such as DetectDefenseType) to call SetActivity when it needs to specifically change the activity. But for the other activities, we would simply stop processing and have IsComplete() return true. Now, the iterative method (in Robot) can look something like this:

if(! currentActivity.isComplete())
{
    currentActivity.Update();
}
else
{
    // code to determine the next appropriate activity...
}

If you want, you can encapsulate this in your GameState classes for the top level (instead of having an Autonomy, ManualDrive, and Idle activity).

In an activity like BreachSimpleDefense, you will have the same code inside update(), except that the else branch would obviously contain code to go through steps 1 to 5 above (step 6 would be setting isComplete to true).

Either way, the idea is to reuse activity concept for sub-activities. Each activity just uses the sub-activities in different combinations.

primetoxinz commented 8 years ago

should all activities implement IRobotActivity? if not we'd have to check each currentActitvity for implementing it and if it should check for isComplete() to update() or not.

Is realize that it's important to have all activities implement it, or the subactivities for each sequence would have to implement it separately. just easier to have them all implement

Seairth commented 8 years ago

Good point! isComplete() wouldn't be added to the IRobotActivity. It would just be a new method on the Activity base class. At which point, you would only need to implement IRobotActivity on activities where sub-activities require the setActivity functionality. Further, it makes me realize that we probably need to refactor IRobotActivity into two classes:

Then, if you have a need for an activity to provide the same kind of statefulness as Robot, it would extend IActivityState to provide its own getXXXActivity() methods.

primetoxinz commented 8 years ago

Another thing I have noticed in setting this up. Activities have the IRobotActivity passed through the constructor, which are actually the Robot. is it safe to cast this IRobotActivity to Robot to gain access to the Controllers in the Robot class?

Seairth commented 8 years ago

Yes, you could potentially cast. However, a more robust approach would be to pass the robot as a separate constructor parameter. The obvious thing about this is that you are passing robot twice to the same constructor! But the activity class doesn't know that. It only knows that it's getting an IRobotActivity and a robot. That way, suppose you needed to refactor the robot to move the IRobotActivity functionality into the Auto game state. Now, you would be able to pass the Auto and Robot objects without changing all of the activity constructors.

If we were being more strict, we would define an IRobotControllers interface that gave access to those controller instances. You would then pass an IRobotActivity and an IRobotControllers object to the constructors. Now you can safely refactor code in Robot without having to rewrite any of the activities! This is why you use interfaces. You will often see the term "code contract" to describe what we are talking about. In this case, the activities access other objects based on the stated interface (the contract), not the actual class. This way, the class can be completely rewritten, as long as it still presents the same interface to the objects that access it. Even if you move the interface to another class (e.g. Auto) and pass that instead!

Seairth commented 8 years ago

We will want a dedicated MoveForward, whose only purpose is to move forward for a specified period of time. On the other hand, ClearDefense is moving forward until the rear sensors are clear of the shield. Yes, they are both moving forward (and will make use of the same controllers), but their test conditions are completely different.