Chief Delphi

Chief Delphi (http://www.chiefdelphi.com/forums/index.php)
-   Programming (http://www.chiefdelphi.com/forums/forumdisplay.php?f=51)
-   -   Reducing code complexity (http://www.chiefdelphi.com/forums/showthread.php?t=116542)

Jared Russell 02-05-2013 11:31

Re: Reducing code complexity
 
Here is the enum pattern that I like using with Java ME:

Code:

public class MyEnum
{
 private final String mName;

 public static final MyEnum kMyFirstVal = new MyEnum("kMyFirstVal");
 public static final MyEnum kMySecondVal = new MyEnum("kMySecondVal");
 // Insert more enum values here

 private MyEnum(String aName)
 {
  mName = aName;
 }
 
 public String toString()
 {
  return mName;
 }

}

The single biggest challenge when writing code is development time and testing. Everything I do, I do with the thought that "I might need to tweak this in between matches, so I better make it easy to understand and find what I need". If I bonk my head and forget the code entirely, or if I get hit by a bus and the team finds a new programming mentor, I want to be sure that it is fairly obvious what I am doing.

To that end, here is my general technique for writing code (C++ or Java) for FRC:

* I love using scripting - reading properties from a file, reading autonomous modes from a file, etc. You should never need to recompile your code to change a PID gain! Just be sure to put your properties files into source control along with your code.

* You MUST use source control. CVS, SVN, git...don't really care which, but USE IT!

* I only use 2 threads in my user code (well, 3...I let the compressor have its own thread too). One thread does the typical periodic loop stuff. The other thread runs at a high speed (100Hz this year) and does only real-time control stuff (filtering and control loops). For this reason, I dislike using the built-in Command templates and PID controller. More threads = more potential for problems (race conditions, deadlock, synchronization, etc.)

* I love whitespace and useful comments. I hate binary operators and "if ? then : else" syntax - it is too easy to misread. I always put "{ ... }" on my if/else logic...even if it is only one line's worth of code.

* I dislike putting any constants (other than obvious mathematical tautologies...like a circumference being equal to pi*diameter) inline in code. Instead, I put all of my constants (even things like PWM ports) in a single Constants.h/Constants.java file. If I think I will ever need to change the value on the fly, I will put it in a properties file instead.

* My preferred architecture uses three primary interfaces: Subsystems, Loops, and Autonomous States. Subsystems are things like "drive", "arm", "shooter". Loops are things like "arm position controller", "drive straight controller", "auto aim shooter speed controller". Autonomous States are things like "drive for a specified distance", which in many cases involves activating a control loop for a given subsystem (but having these loops separate from the autonomous logic lets me invoke the same behavior during teleop...e.g. arm moving to setpoints, or auto aiming)

* Each subsystem gets a class/file. Each autonomous mode "state" gets its own class/file. Each control loop gets its own class/file.

* At most one autonomous state is active at any time. If I want to do two things at once, then I make a new state that does both. It's just less error prone this way.

* At most one control loop is active for a given subsystem at any time (e.g. I have a drive straight controller, a turn in place controller, a vision-aided aiming controller for the drive...only one at a time!).

* All variables and methods get descriptive names. All member variables start with "m". All function arguments start with "a". All constants start with "k". All variables that represent a real world quantity get units appended to their name, e.g. "mDesiredDistanceMeters".

* For utilities that don't interact directly with WPIlib, I prefer to write code that is valid for both J2SE and J2ME. For example, our trajectory generation library can be copied and pasted into a J2SE project so I can test it.

* I put ALL driver input processing into a single method that is called by teleopPeriodic. But the logic in this method is fairly simple (lots of if/else stuff, but it calls out to methods for specific subsystems to actually do anything).

* I only have one method per motor to actually set the PWM output. This method is then called by other methods in a subsystem. This way, if I need to reverse a direction I only have to worry about a single negative sign.

MrRoboSteve 02-05-2013 11:33

Re: Reducing code complexity
 
Joe's example gives a good demonstration of how the command-based framework lets you abstract away the details and see the high level function of the robot.

It's not the only way to do it -- you can certainly write your own framework -- but it's good enough that you can instead focus on robot functionality. You also get the benefit of others knowing how it works and helping out if needed.

Joe Ross 02-05-2013 11:35

Re: Reducing code complexity
 
Quote:

Originally Posted by Ginto8 (Post 1271197)
This will fulfill the second sequence:
Code:

addParallel(new MoveAngle());
addParallel(new SpinUpShooter());
addSequential(new WaitForChildren());
addSequential(new Shoot());


Interesting. That seems simpler then doing this: http://wpilib.screenstepslive.com/s/...g-two-commands

Ginto8 02-05-2013 17:24

Re: Reducing code complexity
 
Quote:

Originally Posted by Joe Ross (Post 1271302)
Interesting. That seems simpler then doing this: http://wpilib.screenstepslive.com/s/...g-two-commands

There are advantages of doing that over what I suggested. WaitForChildren waits for the termination of all parallel commands. If you add it to something that includes parallel commands intended to run over a longer period of time, you can have issues. And really, their complexity is pretty similar:
Code:

CommandGroup prepShoot = new CommandGroup();
prepShoot.addParallel(new MoveAngle());
prepShoot.addParallel(new SpinUpShooter());
addSequential(prepShoot);
addSequential(new Shoot());


SoftwareBug2.0 02-05-2013 22:29

Re: Reducing code complexity
 
Quote:

Originally Posted by Ginto8 (Post 1271498)
There are advantages of doing that over what I suggested. WaitForChildren waits for the termination of all parallel commands. If you add it to something that includes parallel commands intended to run over a longer period of time, you can have issues. And really, their complexity is pretty similar:
Code:

CommandGroup prepShoot = new CommandGroup();
prepShoot.addParallel(new MoveAngle());
prepShoot.addParallel(new SpinUpShooter());
addSequential(prepShoot);
addSequential(new Shoot());


What would you do if you wanted to have a variable update when steps finished, so that you could display it or something?

I guess you'd wrapperize all of the command classes?

Code:

template<typename T>
class Notify{
        T t;
        bool *done;//not owned

        Notify(bool *b):done(b){}

        void run(){ t.run(); }

        bool done(){
                bool r=t.done();
                if(r){
                        *done=1;
                }
                return r;
        }
};

class MoveAngle{};
class SpinUpShooter{};
class Shoot{};

bool angle_done=0,spin_up_done=0,shoot_done=0;
CommandGroup prepShoot = new CommandGroup();
prepShoot.addParallel(new Notify<MoveAngle>(&angle_done));
prepShoot.addParallel(new Notify<SpinUpShooter>(&spin_up_done));
addSequential(prepShoot);
addSequential(new Notify<Shoot>(&shoot_done));

Is there some easier way that I'm missing?


All times are GMT -5. The time now is 16:26.

Powered by vBulletin® Version 3.6.4
Copyright ©2000 - 2017, Jelsoft Enterprises Ltd.
Copyright © Chief Delphi