示例的模拟脚本sim.ccAgent运行脚本位于同一个目录下,该脚本主要定义了在NS3中如何定义环境以及如何实现其与Gym通信的接口,以下是注释形式的代码讲解:

// 包含头文件,这些头文件包含在build目录下ns3的目录,在执行ns3脚本时,系统会自动进入这个目录,包含以下两个头文件 
#include "ns3/core-module.h"
#include "ns3/opengym-module.h"

// 使用这个命名空间
using namespace ns3;

// 定义一个记录为Opengym
NS_LOG_COMPONENT_DEFINE ("OpenGym");

// 以下几步为定义自己的接口函数,在主函数会将这些函数的回调进行绑定
// 定义状态空间
Ptr<OpenGymSpace> MyGetObservationSpace(void)
{
  uint32_t nodeNum = 5;
  float low = 0.0;
  float high = 10.0;
  std::vector<uint32_t> shape = {nodeNum,};
  std::string dtype = TypeNameGet<uint32_t> ();
  Ptr<OpenGymBoxSpace> space = CreateObject<OpenGymBoxSpace> (low, high, shape, dtype);
  NS_LOG_UNCOND ("MyGetObservationSpace: " << space);
  return space;
}

// 定义动作空间
Ptr<OpenGymSpace> MyGetActionSpace(void)
{
  uint32_t nodeNum = 5;
  Ptr<OpenGymDiscreteSpace> space = CreateObject<OpenGymDiscreteSpace> (nodeNum);
  NS_LOG_UNCOND ("MyGetActionSpace: " << space);
  return space;
}

// 定义游戏结束条件
bool MyGetGameOver(void)
{
  bool isGameOver = false;
  bool test = false;
  static float stepCounter = 0.0;
  stepCounter += 1;

  // 试验次数超过十次后游戏结束
  if (stepCounter == 10 && test) {
      isGameOver = true;
  }
  NS_LOG_UNCOND ("MyGetGameOver: " << isGameOver);
  return isGameOver;
}

// 得到观测
Ptr<OpenGymDataContainer> MyGetObservation(void)
{
  uint32_t nodeNum = 5;
  uint32_t low = 0.0;
  uint32_t high = 10.0;

  // 随机变量
  Ptr<UniformRandomVariable> rngInt = CreateObject<UniformRandomVariable> ();

  std::vector<uint32_t> shape = {nodeNum,};
  Ptr<OpenGymBoxContainer<uint32_t> > box = CreateObject<OpenGymBoxContainer<uint32_t> >(shape);

  // 生成随机的观测数据
  for (uint32_t i = 0; i<nodeNum; i++){
    uint32_t value = rngInt->GetInteger(low, high);
    box->AddValue(value);
  }

  NS_LOG_UNCOND ("MyGetObservation: " << box);
  return box;
}

// 得到回报函数
float MyGetReward(void)
{
  static float reward = 0.0;
  reward += 1;
  return reward;
}

// 得到额外的信息
std::string MyGetExtraInfo(void)
{
  std::string myInfo = "testInfo";
  myInfo += "|123";
  NS_LOG_UNCOND("MyGetExtraInfo: " << myInfo);
  return myInfo;
}

// 执行动作
bool MyExecuteActions(Ptr<OpenGymDataContainer> action)
{
  // 其实就是做了个变量类型转化,并没有真正地执行动作
  Ptr<OpenGymDiscreteContainer> discrete = DynamicCast<OpenGymDiscreteContainer>(action);
  NS_LOG_UNCOND ("MyExecuteActions: " << action);
  return true;
}

// 递归对自己进行调用
void ScheduleNextStateRead(double envStepTime, Ptr<OpenGymInterface> openGym)
{
  Simulator::Schedule (Seconds(envStepTime), &ScheduleNextStateRead, envStepTime, openGym);

  // 这个函数将当前的状态返回
  openGym->NotifyCurrentState();
}

int
main (int argc, char *argv[])
{
  // 随机函数种子
  uint32_t simSeed = 1;

  // 这里定义模拟的总时间,也就是10秒
  double simulationTime = 0.5;

  // 这里定义每一步执行的时间,为0.1秒
  double envStepTime = 0.1;
  uint32_t openGymPort = 5555;
  uint32_t testArg = 0;
  CommandLine cmd;
    
  // 必选参数
  cmd.AddValue ("openGymPort", "Port number for OpenGym env. Default: 5555", openGymPort);
  cmd.AddValue ("simSeed", "Seed for random generator. Default: 1", simSeed);

  // 可选参数
  cmd.AddValue ("simTime", "Simulation time in seconds. Default: 10s", simulationTime);
  cmd.AddValue ("testArg", "Extra simulation argument. Default: 0", testArg);
  cmd.Parse (argc, argv);

  NS_LOG_UNCOND("Ns3Env parameters:");
  NS_LOG_UNCOND("--simulationTime: " << simulationTime);
  NS_LOG_UNCOND("--openGymPort: " << openGymPort);
  NS_LOG_UNCOND("--envStepTime: " << envStepTime);
  NS_LOG_UNCOND("--seed: " << simSeed);
  NS_LOG_UNCOND("--testArg: " << testArg);

  // 随机数种子使用
  RngSeedManager::SetSeed (1);
  RngSeedManager::SetRun (simSeed);

  // 定义接口类,并通过接口类挂载7个必须实现的函数
  // 在创建对象时,需指明接口的端口号,默认为5555
  Ptr<OpenGymInterface> openGym = CreateObject<OpenGymInterface> (openGymPort);
  openGym->SetGetActionSpaceCb( MakeCallback (&MyGetActionSpace) );
  openGym->SetGetObservationSpaceCb( MakeCallback (&MyGetObservationSpace) );
  openGym->SetGetGameOverCb( MakeCallback (&MyGetGameOver) );
  openGym->SetGetObservationCb( MakeCallback (&MyGetObservation) );
  openGym->SetGetRewardCb( MakeCallback (&MyGetReward) );
  openGym->SetGetExtraInfoCb( MakeCallback (&MyGetExtraInfo) );
  openGym->SetExecuteActionsCb( MakeCallback (&MyExecuteActions) );

  // 在模拟时需指明接口类的对象地址
  // 这是一个递归调用的过程,该函数每过envStepTime秒的时间被调用一次,从而不停地向gym端发送当前的数据
  Simulator::Schedule (Seconds(0.0), &ScheduleNextStateRead, envStepTime, openGym);

  NS_LOG_UNCOND ("Simulation start");

  // 在执行simulationTime秒后停止
  Simulator::Stop (Seconds (simulationTime));
  Simulator::Run ();
  NS_LOG_UNCOND ("Simulation stop");

  // 同步执行,而非异步执行,执行结束后通知客户端该模拟已经结束
  openGym->NotifySimulationEnd();

  // 释放相应的空间
  Simulator::Destroy ();
}