Browse Source

PipeUtils changes

tags/1.9.4
falkTX 11 years ago
parent
commit
7194cf6695
10 changed files with 196 additions and 137 deletions
  1. +5
    -5
      source/tests/Utils.cpp
  2. +1
    -1
      source/utils/CarlaBackendUtils.hpp
  3. +1
    -1
      source/utils/CarlaBridgeUtils.hpp
  4. +1
    -1
      source/utils/CarlaLadspaUtils.hpp
  5. +1
    -1
      source/utils/CarlaOscUtils.hpp
  6. +183
    -124
      source/utils/CarlaPipeUtils.hpp
  7. +1
    -1
      source/utils/CarlaRingBuffer.hpp
  8. +1
    -1
      source/utils/CarlaStateUtils.hpp
  9. +1
    -1
      source/utils/CarlaString.hpp
  10. +1
    -1
      source/utils/RtLinkedList.hpp

+ 5
- 5
source/tests/Utils.cpp View File

@@ -1,6 +1,6 @@
/* /*
* Carla Tests
* Copyright (C) 2013 Filipe Coelho <falktx@falktx.com>
* Carla Utility Tests
* Copyright (C) 2013-2014 Filipe Coelho <falktx@falktx.com>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as * modify it under the terms of the GNU General Public License as
@@ -25,7 +25,7 @@
// #include "CarlaLibUtils.hpp" // #include "CarlaLibUtils.hpp"
// #include "CarlaLv2Utils.hpp" // #include "CarlaLv2Utils.hpp"
#include "CarlaOscUtils.hpp" #include "CarlaOscUtils.hpp"
// #include "CarlaPipeUtils.hpp"
#include "CarlaPipeUtils.hpp"
// #include "CarlaShmUtils.hpp" // #include "CarlaShmUtils.hpp"
// #include "CarlaStateUtils.hpp" // #include "CarlaStateUtils.hpp"
// #include "CarlaVstUtils.hpp" // #include "CarlaVstUtils.hpp"
@@ -52,8 +52,8 @@ struct MyStruct {
class MyLeakCheckedClass class MyLeakCheckedClass
{ {
public: public:
MyLeakCheckedClass() {}
~MyLeakCheckedClass() {}
MyLeakCheckedClass() noexcept {}
~MyLeakCheckedClass() noexcept {}
private: private:
char pad[100]; char pad[100];
int i; int i;


+ 1
- 1
source/utils/CarlaBackendUtils.hpp View File

@@ -1,6 +1,6 @@
/* /*
* Carla Backend utils * Carla Backend utils
* Copyright (C) 2011-2013 Filipe Coelho <falktx@falktx.com>
* Copyright (C) 2011-2014 Filipe Coelho <falktx@falktx.com>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as * modify it under the terms of the GNU General Public License as


+ 1
- 1
source/utils/CarlaBridgeUtils.hpp View File

@@ -1,6 +1,6 @@
/* /*
* Carla Bridge utils * Carla Bridge utils
* Copyright (C) 2013 Filipe Coelho <falktx@falktx.com>
* Copyright (C) 2013-2014 Filipe Coelho <falktx@falktx.com>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as * modify it under the terms of the GNU General Public License as


+ 1
- 1
source/utils/CarlaLadspaUtils.hpp View File

@@ -1,6 +1,6 @@
/* /*
* Carla LADSPA utils * Carla LADSPA utils
* Copyright (C) 2011-2013 Filipe Coelho <falktx@falktx.com>
* Copyright (C) 2011-2014 Filipe Coelho <falktx@falktx.com>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as * modify it under the terms of the GNU General Public License as


+ 1
- 1
source/utils/CarlaOscUtils.hpp View File

@@ -1,6 +1,6 @@
/* /*
* Carla OSC utils * Carla OSC utils
* Copyright (C) 2012-2013 Filipe Coelho <falktx@falktx.com>
* Copyright (C) 2012-2014 Filipe Coelho <falktx@falktx.com>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as * modify it under the terms of the GNU General Public License as


+ 183
- 124
source/utils/CarlaPipeUtils.hpp View File

@@ -1,7 +1,7 @@
/* /*
* Carla Pipe utils based on lv2fil UI code * Carla Pipe utils based on lv2fil UI code
* Copyright (C) 2009 Nedko Arnaudov <nedko@arnaudov.name> * Copyright (C) 2009 Nedko Arnaudov <nedko@arnaudov.name>
* Copyright (C) 2013 Filipe Coelho <falktx@falktx.com>
* Copyright (C) 2013-2014 Filipe Coelho <falktx@falktx.com>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as * modify it under the terms of the GNU General Public License as
@@ -19,10 +19,6 @@
#ifndef CARLA_PIPE_UTILS_HPP_INCLUDED #ifndef CARLA_PIPE_UTILS_HPP_INCLUDED
#define CARLA_PIPE_UTILS_HPP_INCLUDED #define CARLA_PIPE_UTILS_HPP_INCLUDED


#define WAIT_START_TIMEOUT 3000 /* ms */
#define WAIT_ZOMBIE_TIMEOUT 3000 /* ms */
#define WAIT_STEP 100 /* ms */

#include "CarlaUtils.hpp" #include "CarlaUtils.hpp"
#include "CarlaString.hpp" #include "CarlaString.hpp"


@@ -33,6 +29,10 @@
#include <signal.h> #include <signal.h>
#include <sys/wait.h> #include <sys/wait.h>


#define WAIT_START_TIMEOUT 3000 /* ms */
#define WAIT_ZOMBIE_TIMEOUT 3000 /* ms */
#define WAIT_STEP 100 /* ms */

// ----------------------------------------------------------------------- // -----------------------------------------------------------------------


class CarlaPipeServer class CarlaPipeServer
@@ -45,6 +45,8 @@ protected:
fReading(false) fReading(false)
{ {
carla_debug("CarlaPipeServer::CarlaPipeServer()"); carla_debug("CarlaPipeServer::CarlaPipeServer()");

fTmpBuf[0xff] = '\0';
} }


// ------------------------------------------------------------------- // -------------------------------------------------------------------
@@ -57,12 +59,12 @@ public:
stop(); stop();
} }


void start(const char* const filename, const char* const arg1, const char* const arg2)
bool start(const char* const filename, const char* const arg1, const char* const arg2)
{ {
CARLA_SAFE_ASSERT_RETURN(filename != nullptr,);
CARLA_SAFE_ASSERT_RETURN(arg1 != nullptr,);
CARLA_SAFE_ASSERT_RETURN(arg2 != nullptr,);
carla_debug("CarlaPipeServer::start(\"%s\", \"%s\", \"%s\"", filename, arg1, arg2);
CARLA_SAFE_ASSERT_RETURN(filename != nullptr && filename[0] != '\0', false);
CARLA_SAFE_ASSERT_RETURN(arg1 != nullptr, false);
CARLA_SAFE_ASSERT_RETURN(arg2 != nullptr, false);
carla_debug("CarlaPipeServer::start(\"%s\", \"%s\", \"%s\")", filename, arg1, arg2);


//---------------------------------------------------------------- //----------------------------------------------------------------


@@ -85,23 +87,25 @@ public:
int pipe1[2]; // written by host process, read by plugin UI process int pipe1[2]; // written by host process, read by plugin UI process
int pipe2[2]; // written by plugin UI process, read by host process int pipe2[2]; // written by plugin UI process, read by host process


if (pipe(pipe1) != 0)
if (::pipe(pipe1) != 0)
{ {
fail("pipe1 creation failed"); fail("pipe1 creation failed");
return;
return false;
} }


if (pipe(pipe2) != 0)
if (::pipe(pipe2) != 0)
{ {
::close(pipe1[0]);
::close(pipe1[1]);
fail("pipe2 creation failed"); fail("pipe2 creation failed");
return;
return false;
} }


char uiPipeRecv[100+1]; char uiPipeRecv[100+1];
char uiPipeSend[100+1]; char uiPipeSend[100+1];


std::snprintf(uiPipeRecv, 100, "%d", pipe1[0]); /* [0] means reading end */
std::snprintf(uiPipeSend, 100, "%d", pipe2[1]); /* [1] means writting end */
std::snprintf(uiPipeRecv, 100, "%d", pipe1[0]); // [0] means reading end
std::snprintf(uiPipeSend, 100, "%d", pipe2[1]); // [1] means writting end


uiPipeRecv[100] = '\0'; uiPipeRecv[100] = '\0';
uiPipeSend[100] = '\0'; uiPipeSend[100] = '\0';
@@ -116,92 +120,130 @@ public:


if ((! fork_exec(argv, &ret)) || ret == -1) if ((! fork_exec(argv, &ret)) || ret == -1)
{ {
close(pipe1[0]);
close(pipe1[1]);
close(pipe2[0]);
close(pipe2[1]);
::close(pipe1[0]);
::close(pipe1[1]);
::close(pipe2[0]);
::close(pipe2[1]);
fail("fork_exec() failed"); fail("fork_exec() failed");
return;
return false;
} }


fPid = ret; fPid = ret;


/* fork duplicated the handles, close pipe ends that are used by the child process */
close(pipe1[0]);
close(pipe2[1]);
// fork duplicated the handles, close pipe ends that are used by the child process
::close(pipe1[0]);
::close(pipe2[1]);


fPipeSend = pipe1[1]; /* [1] means writting end */
fPipeRecv = pipe2[0]; /* [0] means reading end */
fPipeSend = pipe1[1]; // [1] means writting end
fPipeRecv = pipe2[0]; // [0] means reading end


fcntl(fPipeRecv, F_SETFL, fcntl(fPipeRecv, F_GETFL) | O_NONBLOCK);
// set non-block
try {
ret = fcntl(fPipeRecv, F_SETFL, fcntl(fPipeRecv, F_GETFL) | O_NONBLOCK);
} catch (...) {
ret = -1;
fail("failed to set pipe as non-block");
}


//---------------------------------------------------------------- //----------------------------------------------------------------
// wait a while for child process to confirm it is alive // wait a while for child process to confirm it is alive


char ch;

for (int i=0; ;)
if (ret != -1)
{ {
ssize_t ret2 = read(fPipeRecv, &ch, 1);
char ch;
ssize_t ret2;


switch (ret2)
for (int i=0; ;)
{ {
case -1:
if (errno == EAGAIN)
try {
ret2 = ::read(fPipeRecv, &ch, 1);
}
catch (...) {
fail("failed to read from pipe");
break;
}

switch (ret2)
{ {
if (i < WAIT_START_TIMEOUT / WAIT_STEP)
case -1:
if (errno == EAGAIN)
{ {
carla_msleep(WAIT_STEP);
i++;
continue;
if (i < WAIT_START_TIMEOUT / WAIT_STEP)
{
carla_msleep(WAIT_STEP);
++i;
continue;
}
carla_stderr("we have waited for child with pid %d to appear for %.1f seconds and we are giving up", (int)fPid, (float)WAIT_START_TIMEOUT / 1000.0f);
}
else
carla_stderr("read() failed: %s", std::strerror(errno));
break;

case 1:
if (ch == '\n') {
// success
return true;
} }
carla_stderr("read() has wrong first char '%c'", ch);
break;


carla_stderr("we have waited for child with pid %d to appear for %.1f seconds and we are giving up", (int)fPid, (float)WAIT_START_TIMEOUT / 1000.0f);
default:
carla_stderr("read() returned %i", int(ret2));
break;
} }
else
carla_stderr("read() failed: %s", strerror(errno));
break;

case 1:
if (ch == '\n')
// success
return;


carla_stderr("read() wrong first char '%c'", ch);
break;

default:
carla_stderr("read() returned %d", ret2);
break; break;
} }


break;
carla_stderr("force killing misbehaved child %i (start)", int(fPid));
} }


carla_stderr("force killing misbehaved child %d (start)", (int)fPid);

if (kill(fPid, SIGKILL) == -1) if (kill(fPid, SIGKILL) == -1)
{ {
carla_stderr("kill() failed: %s (start)\n", strerror(errno)); carla_stderr("kill() failed: %s (start)\n", strerror(errno));
} }


/* wait a while child to exit, we dont like zombie processes */
// wait a while child to exit, we dont like zombie processes
wait_child(fPid); wait_child(fPid);

// close pipes
try {
::close(fPipeRecv);
} catch (...) {}

try {
::close(fPipeSend);
} catch (...) {}

fPipeRecv = -1;
fPipeSend = -1;
fPid = -1;

return false;
} }


void stop()
void stop() noexcept
{ {
carla_debug("CarlaPipeServer::stop()"); carla_debug("CarlaPipeServer::stop()");


if (fPipeSend == -1 || fPipeRecv == -1 || fPid == -1) if (fPipeSend == -1 || fPipeRecv == -1 || fPid == -1)
return; return;


write(fPipeSend, "quit\n", 5);
try {
::write(fPipeSend, "quit\n", 5);
} catch (...) {}


waitChildClose(); waitChildClose();


close(fPipeRecv);
close(fPipeSend);
try {
::close(fPipeRecv);
} catch (...) {}

try {
::close(fPipeSend);
} catch (...) {}

fPipeRecv = -1; fPipeRecv = -1;
fPipeSend = -1; fPipeSend = -1;
fPid = -1; fPid = -1;
@@ -209,18 +251,18 @@ public:


void idle() void idle()
{ {
char* locale = nullptr;
const char* locale = nullptr;


for (;;) for (;;)
{ {
char* const msg = readline();
const char* const msg(readline());


if (msg == nullptr) if (msg == nullptr)
break; break;


if (locale == nullptr) if (locale == nullptr)
{ {
locale = strdup(setlocale(LC_NUMERIC, nullptr));
locale = carla_strdup(setlocale(LC_NUMERIC, nullptr));
setlocale(LC_NUMERIC, "POSIX"); setlocale(LC_NUMERIC, "POSIX");
} }


@@ -228,13 +270,13 @@ public:
msgReceived(msg); msgReceived(msg);
fReading = false; fReading = false;


std::free(msg);
delete[] msg;
} }


if (locale != nullptr) if (locale != nullptr)
{ {
setlocale(LC_NUMERIC, locale); setlocale(LC_NUMERIC, locale);
std::free(locale);
delete[] locale;
} }
} }


@@ -242,13 +284,12 @@ public:


bool readNextLineAsBool(bool& value) bool readNextLineAsBool(bool& value)
{ {
if (! fReading)
return false;
CARLA_SAFE_ASSERT_RETURN(fReading, false);


if (char* const msg = readline())
if (const char* const msg = readline())
{ {
value = (std::strcmp(msg, "true") == 0); value = (std::strcmp(msg, "true") == 0);
std::free(msg);
delete[] msg;
return true; return true;
} }


@@ -257,13 +298,12 @@ public:


bool readNextLineAsInt(int& value) bool readNextLineAsInt(int& value)
{ {
if (! fReading)
return false;
CARLA_SAFE_ASSERT_RETURN(fReading, false);


if (char* const msg = readline())
if (const char* const msg = readline())
{ {
value = std::atoi(msg); value = std::atoi(msg);
std::free(msg);
delete[] msg;
return true; return true;
} }


@@ -272,25 +312,23 @@ public:


bool readNextLineAsFloat(float& value) bool readNextLineAsFloat(float& value)
{ {
if (! fReading)
return false;
CARLA_SAFE_ASSERT_RETURN(fReading, false);


if (char* const msg = readline())
if (const char* const msg = readline())
{ {
bool ret = (std::sscanf(msg, "%f", &value) == 1);
std::free(msg);
const bool ret(std::sscanf(msg, "%f", &value) == 1);
delete[] msg;
return ret; return ret;
} }


return false; return false;
} }


bool readNextLineAsString(char*& value)
bool readNextLineAsString(const char*& value)
{ {
if (! fReading)
return false;
CARLA_SAFE_ASSERT_RETURN(fReading, false);


if (char* const msg = readline())
if (const char* const msg = readline())
{ {
value = msg; value = msg;
return true; return true;
@@ -299,40 +337,54 @@ public:
return false; return false;
} }


void writeMsg(const char* const msg)
// -------------------------------------------------------------------

void writeMsg(const char* const msg) const noexcept
{ {
::write(fPipeSend, msg, std::strlen(msg));
CARLA_SAFE_ASSERT_RETURN(fPipeSend != -1,);

try {
::write(fPipeSend, msg, std::strlen(msg));
} catch (...) {}
} }


void writeMsg(const char* const msg, size_t size)
void writeMsg(const char* const msg, size_t size) const noexcept
{ {
::write(fPipeSend, msg, size);
CARLA_SAFE_ASSERT_RETURN(fPipeSend != -1,);

try {
::write(fPipeSend, msg, size);
} catch (...) {}
} }


void writeAndFixMsg(const char* const msg) void writeAndFixMsg(const char* const msg)
{ {
const size_t size = std::strlen(msg);
CARLA_SAFE_ASSERT_RETURN(fPipeSend != -1,);


char smsg[size+1];
std::strcpy(smsg, msg);
const size_t size(std::strlen(msg));


smsg[size-1] = '\n';
smsg[size] = '\0';
char fixedMsg[size+1];
std::strcpy(fixedMsg, msg);


for (size_t i=0; i<size; ++i)
for (size_t i=0; i < size; ++i)
{ {
if (smsg[i] == '\n')
smsg[i] = '\r';
if (fixedMsg[i] == '\n')
fixedMsg[i] = '\r';
} }


::write(fPipeSend, smsg, size+1);
fixedMsg[size-1] = '\n';
fixedMsg[size] = '\0';

try {
::write(fPipeSend, fixedMsg, size);
} catch (...) {}
} }


void waitChildClose() void waitChildClose()
{ {
if (! wait_child(fPid)) if (! wait_child(fPid))
{ {
carla_stderr2("force killing misbehaved child %d (exit)", (int)fPid);
carla_stderr2("force killing misbehaved child %i (exit)", int(fPid));


if (kill(fPid, SIGKILL) == -1) if (kill(fPid, SIGKILL) == -1)
carla_stderr2("kill() failed: %s (exit)", strerror(errno)); carla_stderr2("kill() failed: %s (exit)", strerror(errno));
@@ -358,24 +410,28 @@ private:
int fPipeSend; // the pipe end that is used for sending messages to UI int fPipeSend; // the pipe end that is used for sending messages to UI
pid_t fPid; pid_t fPid;
bool fReading; bool fReading;
CarlaString fTempBuf;

char fTmpBuf[0xff+1];
CarlaString fTmpStr;


// ------------------------------------------------------------------- // -------------------------------------------------------------------


char* readline()
const char* readline()
{ {
char ch;
char ch;
char* ptr = fTmpBuf;
ssize_t ret; ssize_t ret;


char buf[0xff+1];
char* ptr = buf;

fTempBuf.clear();
buf[0xff] = '\0';
fTmpStr.clear();


for (int i=0;; ++i)
for (int i=0; i < 0xff; ++i)
{ {
ret = read(fPipeRecv, &ch, 1);
try {
ret = read(fPipeRecv, &ch, 1);
}
catch (...) {
break;
}


if (ret == 1 && ch != '\n') if (ret == 1 && ch != '\n')
{ {
@@ -387,22 +443,22 @@ private:
if (i+1 == 0xff) if (i+1 == 0xff)
{ {
i = 0; i = 0;
ptr = buf;
fTempBuf += buf;
ptr = fTmpBuf;
fTmpStr += fTmpBuf;
} }


continue; continue;
} }


if (fTempBuf.isNotEmpty() || ptr != buf)
if (fTmpStr.isNotEmpty() || ptr != fTmpBuf)
{ {
if (ptr != buf)
if (ptr != fTmpBuf)
{ {
*ptr = '\0'; *ptr = '\0';
fTempBuf += buf;
fTmpStr += fTmpBuf;
} }


return strdup((const char*)fTempBuf);
return fTmpStr.dup();
} }


break; break;
@@ -411,15 +467,15 @@ private:
return nullptr; return nullptr;
} }


static bool fork_exec(const char* const argv[5], int* const retp)
static bool fork_exec(const char* const argv[5], int* const retp) noexcept
{ {
pid_t ret = *retp = vfork();
const pid_t ret = *retp = vfork();


switch (ret) switch (ret)
{ {
case 0: /* child process */ case 0: /* child process */
execlp(argv[0], argv[0], argv[1], argv[2], argv[3], argv[4], nullptr); execlp(argv[0], argv[0], argv[1], argv[2], argv[3], argv[4], nullptr);
carla_stderr2("exec of UI failed: %s", std::strerror(errno));
carla_stderr2("vfork exec failed: %s", std::strerror(errno));
return false; return false;
case -1: /* error */ case -1: /* error */
carla_stderr2("vfork() failed: %s", std::strerror(errno)); carla_stderr2("vfork() failed: %s", std::strerror(errno));
@@ -431,18 +487,22 @@ private:


static bool wait_child(const pid_t pid) static bool wait_child(const pid_t pid)
{ {
pid_t ret;
int i;

if (pid == -1) if (pid == -1)
{ {
carla_stderr2("Can't wait for pid -1"); carla_stderr2("Can't wait for pid -1");
return false; return false;
} }


for (i = 0; i < WAIT_ZOMBIE_TIMEOUT / WAIT_STEP; ++i)
pid_t ret;

for (int i=0, maxTime=WAIT_ZOMBIE_TIMEOUT/WAIT_STEP; i < maxTime; ++i)
{ {
ret = waitpid(pid, nullptr, WNOHANG);
try {
ret = ::waitpid(pid, nullptr, WNOHANG);
}
catch (...) {
break;
}


if (ret != 0) if (ret != 0)
{ {
@@ -451,19 +511,18 @@ private:


if (ret == -1) if (ret == -1)
{ {
carla_stderr2("waitpid(%d) failed: %s", (int)pid, strerror(errno));
carla_stderr2("waitpid(%i) failed: %s", int(pid), std::strerror(errno));
return false; return false;
} }


carla_stderr2("we have waited for child pid %d to exit but we got pid %d instead", (int)pid, (int)ret);

carla_stderr2("we waited for child pid %i to exit but we got pid %i instead", int(pid), int(ret));
return false; return false;
} }


carla_msleep(WAIT_STEP); /* wait 100 ms */ carla_msleep(WAIT_STEP); /* wait 100 ms */
} }


carla_stderr2("we have waited for child with pid %d to exit for %.1f seconds and we are giving up", (int)pid, (float)WAIT_START_TIMEOUT / 1000.0f);
carla_stderr2("we waited for child with pid %i to exit for %.1f seconds and we are giving up", int(pid), float(WAIT_START_TIMEOUT)/1000.0f);
return false; return false;
} }
}; };


+ 1
- 1
source/utils/CarlaRingBuffer.hpp View File

@@ -1,7 +1,7 @@
/* /*
* Carla Ring Buffer based on dssi-vst code * Carla Ring Buffer based on dssi-vst code
* Copyright (C) 2004-2010 Chris Cannam <cannam@all-day-breakfast.com> * Copyright (C) 2004-2010 Chris Cannam <cannam@all-day-breakfast.com>
* Copyright (C) 2013 Filipe Coelho <falktx@falktx.com>
* Copyright (C) 2013-2014 Filipe Coelho <falktx@falktx.com>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as * modify it under the terms of the GNU General Public License as


+ 1
- 1
source/utils/CarlaStateUtils.hpp View File

@@ -1,6 +1,6 @@
/* /*
* Carla State utils * Carla State utils
* Copyright (C) 2012-2013 Filipe Coelho <falktx@falktx.com>
* Copyright (C) 2012-2014 Filipe Coelho <falktx@falktx.com>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as * modify it under the terms of the GNU General Public License as


+ 1
- 1
source/utils/CarlaString.hpp View File

@@ -1,6 +1,6 @@
/* /*
* Carla String * Carla String
* Copyright (C) 2013 Filipe Coelho <falktx@falktx.com>
* Copyright (C) 2013-2014 Filipe Coelho <falktx@falktx.com>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as * modify it under the terms of the GNU General Public License as


+ 1
- 1
source/utils/RtLinkedList.hpp View File

@@ -1,6 +1,6 @@
/* /*
* High-level, real-time safe, templated, C++ doubly-linked list * High-level, real-time safe, templated, C++ doubly-linked list
* Copyright (C) 2013 Filipe Coelho <falktx@falktx.com>
* Copyright (C) 2013-2014 Filipe Coelho <falktx@falktx.com>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as * modify it under the terms of the GNU General Public License as


Loading…
Cancel
Save