458 lines
9.8 KiB
C++
458 lines
9.8 KiB
C++
/*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
*
|
|
* DmxBuffer.cpp
|
|
* The DmxBuffer class
|
|
* Copyright (C) 2005-2009 Simon Newton
|
|
*
|
|
* This implements a DmxBuffer which uses copy-on-write and delayed init.
|
|
*
|
|
* A DmxBuffer can hold up to 512 bytes of channel information. The amount of
|
|
* valid data is returned by calling Size().
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include <algorithm>
|
|
#include <iostream>
|
|
#include <string>
|
|
#include <vector>
|
|
#include "ola/BaseTypes.h"
|
|
#include "ola/DmxBuffer.h"
|
|
#include "ola/Logging.h"
|
|
#include "ola/StringUtils.h"
|
|
|
|
namespace ola {
|
|
|
|
using std::min;
|
|
using std::max;
|
|
using std::vector;
|
|
|
|
DmxBuffer::DmxBuffer()
|
|
: m_ref_count(NULL),
|
|
m_copy_on_write(false),
|
|
m_data(NULL),
|
|
m_length(0) {
|
|
}
|
|
|
|
|
|
/*
|
|
* Copy constructor. We just copy the underlying pointers and mark COW as
|
|
* true if the other buffer has data.
|
|
*/
|
|
DmxBuffer::DmxBuffer(const DmxBuffer &other)
|
|
: m_ref_count(NULL),
|
|
m_copy_on_write(false),
|
|
m_data(NULL),
|
|
m_length(0) {
|
|
|
|
if (other.m_data && other.m_ref_count) {
|
|
CopyFromOther(other);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Create a new buffer from data
|
|
*/
|
|
DmxBuffer::DmxBuffer(const uint8_t *data, unsigned int length)
|
|
: m_ref_count(0),
|
|
m_copy_on_write(false),
|
|
m_data(NULL),
|
|
m_length(0) {
|
|
Set(data, length);
|
|
}
|
|
|
|
|
|
/*
|
|
* Create a new buffer from a string
|
|
*/
|
|
DmxBuffer::DmxBuffer(const string &data)
|
|
: m_ref_count(0),
|
|
m_copy_on_write(false),
|
|
m_data(NULL),
|
|
m_length(0) {
|
|
Set(data);
|
|
}
|
|
|
|
|
|
/*
|
|
* Cleanup
|
|
*/
|
|
DmxBuffer::~DmxBuffer() {
|
|
CleanupMemory();
|
|
}
|
|
|
|
|
|
/*
|
|
* Make this buffer equal to another one
|
|
* @param other the other DmxBuffer
|
|
*/
|
|
DmxBuffer& DmxBuffer::operator=(const DmxBuffer &other) {
|
|
if (this != &other) {
|
|
CleanupMemory();
|
|
if (other.m_data) {
|
|
CopyFromOther(other);
|
|
}
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
|
|
/*
|
|
* Check for equality.
|
|
*/
|
|
bool DmxBuffer::operator==(const DmxBuffer &other) const {
|
|
return (m_length == other.m_length &&
|
|
(m_data == other.m_data ||
|
|
0 == memcmp(m_data, other.m_data, m_length)));
|
|
}
|
|
|
|
|
|
/*
|
|
* HTP Merge from another DmxBuffer.
|
|
* @param other the DmxBuffer to HTP merge into this one
|
|
*/
|
|
bool DmxBuffer::HTPMerge(const DmxBuffer &other) {
|
|
if (!m_data) {
|
|
if (!Init())
|
|
return false;
|
|
}
|
|
DuplicateIfNeeded();
|
|
|
|
unsigned int other_length = min((unsigned int) DMX_UNIVERSE_SIZE,
|
|
other.m_length);
|
|
unsigned int merge_length = min(m_length, other.m_length);
|
|
|
|
for (unsigned int i = 0; i < merge_length; i++) {
|
|
m_data[i] = max(m_data[i], other.m_data[i]);
|
|
}
|
|
|
|
if (other_length > m_length) {
|
|
memcpy(m_data + merge_length, other.m_data + merge_length,
|
|
other_length - merge_length);
|
|
m_length = other_length;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
/*
|
|
* Set the contents of this DmxBuffer
|
|
* @post Size() == length
|
|
*/
|
|
bool DmxBuffer::Set(const uint8_t *data, unsigned int length) {
|
|
if (!data)
|
|
return false;
|
|
|
|
if (m_copy_on_write)
|
|
CleanupMemory();
|
|
if (!m_data) {
|
|
if (!Init())
|
|
return false;
|
|
}
|
|
m_length = min(length, (unsigned int) DMX_UNIVERSE_SIZE);
|
|
memcpy(m_data, data, m_length);
|
|
return true;
|
|
}
|
|
|
|
|
|
/*
|
|
* Set the contents of this DmxBuffer
|
|
* @param data the string with the dmx data
|
|
* @post Size() == data.length()
|
|
*/
|
|
bool DmxBuffer::Set(const string &data) {
|
|
return Set(reinterpret_cast<const uint8_t*>(data.data()), data.length());
|
|
}
|
|
|
|
|
|
/*
|
|
* Sets the data in this buffer to be the same as the other one.
|
|
* Used instead of a COW to optimise.
|
|
* @post Size() == other.Size()
|
|
*/
|
|
bool DmxBuffer::Set(const DmxBuffer &other) {
|
|
return Set(other.m_data, other.m_length);
|
|
}
|
|
|
|
|
|
/*
|
|
* Convert a ',' separated list into a DmxBuffer. Invalid values are set to
|
|
* 0. 0s can be dropped between the commas.
|
|
* @param input the string to split
|
|
*/
|
|
bool DmxBuffer::SetFromString(const string &input) {
|
|
unsigned int i = 0;
|
|
vector<string> dmx_values;
|
|
vector<string>::const_iterator iter;
|
|
|
|
if (m_copy_on_write)
|
|
CleanupMemory();
|
|
if (!m_data)
|
|
if (!Init())
|
|
return false;
|
|
|
|
if (input.empty()) {
|
|
m_length = 0;
|
|
return true;
|
|
}
|
|
StringSplit(input, dmx_values, ",");
|
|
for (iter = dmx_values.begin();
|
|
iter != dmx_values.end() && i < DMX_UNIVERSE_SIZE; ++iter, ++i) {
|
|
m_data[i] = atoi(iter->data());
|
|
}
|
|
m_length = i;
|
|
return true;
|
|
}
|
|
|
|
|
|
/*
|
|
* Set a Range of data to a single value
|
|
* @param offset the starting channel
|
|
* @param value the value to set the range to
|
|
* @param length the length of the range to set
|
|
*/
|
|
bool DmxBuffer::SetRangeToValue(unsigned int offset,
|
|
uint8_t value,
|
|
unsigned int length) {
|
|
if (offset >= DMX_UNIVERSE_SIZE)
|
|
return false;
|
|
|
|
if (!m_data) {
|
|
Blackout();
|
|
}
|
|
|
|
if (offset > m_length)
|
|
return false;
|
|
|
|
DuplicateIfNeeded();
|
|
|
|
unsigned int copy_length = min(length, DMX_UNIVERSE_SIZE - offset);
|
|
memset(m_data + offset, value, copy_length);
|
|
m_length = max(m_length, offset + copy_length);
|
|
return true;
|
|
}
|
|
|
|
|
|
/*
|
|
* Set a range of data. Calling this on an uninitialized buffer will call
|
|
* Blackout() first. Attempting to set data with an offset > Size() is an
|
|
* error.
|
|
* @param offset the starting channel
|
|
* @param data a pointer to the new data
|
|
* @param length the length of the data
|
|
*/
|
|
bool DmxBuffer::SetRange(unsigned int offset,
|
|
const uint8_t *data,
|
|
unsigned int length) {
|
|
if (!data || offset >= DMX_UNIVERSE_SIZE)
|
|
return false;
|
|
|
|
if (!m_data) {
|
|
Blackout();
|
|
}
|
|
|
|
if (offset > m_length)
|
|
return false;
|
|
|
|
DuplicateIfNeeded();
|
|
|
|
unsigned int copy_length = min(length, DMX_UNIVERSE_SIZE - offset);
|
|
memcpy(m_data + offset, data, copy_length);
|
|
m_length = max(m_length, offset + copy_length);
|
|
return true;
|
|
}
|
|
|
|
|
|
/*
|
|
* Set a single channel. Calling this on an uninitialized buffer will call
|
|
* Blackout() first. Trying to set a channel more than 1 channel past the end
|
|
* of the valid data is an error.
|
|
*/
|
|
void DmxBuffer::SetChannel(unsigned int channel, uint8_t data) {
|
|
if (channel >= DMX_UNIVERSE_SIZE)
|
|
return;
|
|
|
|
if (!m_data) {
|
|
Blackout();
|
|
}
|
|
|
|
if (channel > m_length) {
|
|
OLA_WARN << "attempting to set channel " << channel << "when length is " <<
|
|
m_length;
|
|
return;
|
|
}
|
|
|
|
DuplicateIfNeeded();
|
|
m_data[channel] = data;
|
|
m_length = max(channel+1, m_length);
|
|
}
|
|
|
|
|
|
/*
|
|
* Get the contents of this buffer
|
|
*/
|
|
void DmxBuffer::Get(uint8_t *data, unsigned int *length) const {
|
|
if (m_data) {
|
|
*length = min(*length, m_length);
|
|
memcpy(data, m_data, *length);
|
|
} else {
|
|
*length = 0;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Returns the value of a channel. This returns 0 if the buffer wasn't
|
|
* initialized or the channel was out-of-bounds.
|
|
*/
|
|
uint8_t DmxBuffer::Get(unsigned int channel) const {
|
|
if (m_data && channel < m_length)
|
|
return m_data[channel];
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* Get the contents of the DmxBuffer as a string
|
|
*/
|
|
string DmxBuffer::Get() const {
|
|
string data;
|
|
data.append(reinterpret_cast<char*>(m_data), m_length);
|
|
return data;
|
|
}
|
|
|
|
|
|
/*
|
|
* Set the buffer to all zeros.
|
|
* @post Size() == DMX_UNIVERSE_SIZE
|
|
*/
|
|
bool DmxBuffer::Blackout() {
|
|
if (m_copy_on_write)
|
|
CleanupMemory();
|
|
if (!m_data)
|
|
if (!Init())
|
|
return false;
|
|
memset(m_data, 0, DMX_UNIVERSE_SIZE);
|
|
m_length = DMX_UNIVERSE_SIZE;
|
|
return true;
|
|
}
|
|
|
|
|
|
/*
|
|
* Reset the bufer to hold no data.
|
|
* @post Size() == 0
|
|
*/
|
|
void DmxBuffer::Reset() {
|
|
if (m_data)
|
|
m_length = 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* Convert to a human readable representation
|
|
*/
|
|
string DmxBuffer::ToString() const {
|
|
if (!m_data)
|
|
return "";
|
|
|
|
std::stringstream str;
|
|
str << static_cast<int>(Size()) << ": ";
|
|
for (unsigned int i = 0; i < Size(); i++) {
|
|
if (i)
|
|
str << ",";
|
|
str << static_cast<int>(m_data[i]);
|
|
}
|
|
return str.str();
|
|
}
|
|
|
|
|
|
/*
|
|
* Allocate memory
|
|
*/
|
|
bool DmxBuffer::Init() {
|
|
try {
|
|
m_data = new uint8_t[DMX_UNIVERSE_SIZE];
|
|
} catch (std::bad_alloc &ex) {
|
|
return false;
|
|
}
|
|
try {
|
|
m_ref_count = new unsigned int;
|
|
} catch (std::bad_alloc &ex) {
|
|
delete[] m_data;
|
|
return false;
|
|
}
|
|
m_length = 0;
|
|
*m_ref_count = 1;
|
|
return true;
|
|
}
|
|
|
|
|
|
/*
|
|
* Called before making a change, this duplicates the data if required.
|
|
*/
|
|
bool DmxBuffer::DuplicateIfNeeded() {
|
|
if (m_copy_on_write && *m_ref_count == 1)
|
|
m_copy_on_write = false;
|
|
|
|
if (m_copy_on_write && *m_ref_count > 1) {
|
|
unsigned int *old_ref_count = m_ref_count;
|
|
uint8_t *original_data = m_data;
|
|
unsigned int length = m_length;
|
|
m_copy_on_write = false;
|
|
if (Init()) {
|
|
Set(original_data, length);
|
|
(*old_ref_count)--;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
/*
|
|
* Setup this buffer to point to the data of the other buffer
|
|
* @param other the source buffer
|
|
* @pre other.m_data and other.m_ref_count are not NULL
|
|
*/
|
|
void DmxBuffer::CopyFromOther(const DmxBuffer &other) {
|
|
m_copy_on_write = true;
|
|
other.m_copy_on_write = true;
|
|
m_ref_count = other.m_ref_count;
|
|
(*m_ref_count)++;
|
|
m_data = other.m_data;
|
|
m_length = other.m_length;
|
|
}
|
|
|
|
|
|
/*
|
|
* Decrement the ref count by one and free the memory if required
|
|
*/
|
|
void DmxBuffer::CleanupMemory() {
|
|
if (m_ref_count && m_data) {
|
|
(*m_ref_count)--;
|
|
if (!*m_ref_count) {
|
|
delete[] m_data;
|
|
delete m_ref_count;
|
|
}
|
|
m_data = NULL;
|
|
m_ref_count = NULL;
|
|
m_length = 0;
|
|
}
|
|
}
|
|
} // ola
|