// Filename: geomVertexArrayData.cxx
// Created by:  drose (17Mar05)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
//
// All use of this software is subject to the terms of the Panda 3d
// Software license.  You should have received a copy of this license
// along with this source code; you will also find a current copy of
// the license at http://etc.cmu.edu/panda3d/docs/license/ .
//
// To contact the maintainers of this program write to
// panda3d-general@lists.sourceforge.net .
//
////////////////////////////////////////////////////////////////////

#include "geomVertexArrayData.h"
#include "geom.h"
#include "preparedGraphicsObjects.h"
#include "reversedNumericData.h"
#include "bamReader.h"
#include "bamWriter.h"
#include "pset.h"
#include "config_gobj.h"
#include "pStatTimer.h"
#include "configVariableInt.h"
#include "simpleAllocator.h"
#include "vertexDataBuffer.h"

ConfigVariableInt max_independent_vertex_data
("max-independent-vertex-data", -1,
 PRC_DESC("Specifies the maximum number of bytes of all vertex data "
          "that is independent of the paging system.  This is an "
          "initial buffer before max-ram-vertex-data, specifically "
          "designed for vertex datas that are dynamic in nature and "
          "may change size or be created and destroyed frequently."));

ConfigVariableInt vertex_data_page_size
("vertex-data-page-size", 512,
 PRC_DESC("The number of bytes to allocate at a time for vertex data.  "
          "This also controls the page size that is compressed and written "
          "to disk when vertex data pages are evicted from memory."));

SimpleLru GeomVertexArrayData::_independent_lru("independent", max_independent_vertex_data);
SimpleLru GeomVertexArrayData::_small_lru("small", max_independent_vertex_data);

VertexDataBook GeomVertexArrayData::_book(vertex_data_page_size);


TypeHandle GeomVertexArrayData::_type_handle;
TypeHandle GeomVertexArrayData::CData::_type_handle;
TypeHandle GeomVertexArrayDataHandle::_type_handle;

ALLOC_DELETED_CHAIN_DEF(GeomVertexArrayDataHandle);

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayData::Default Constructor
//       Access: Private
//  Description: Constructs an invalid object.  This is only used when
//               reading from the bam file.
////////////////////////////////////////////////////////////////////
GeomVertexArrayData::
GeomVertexArrayData() : SimpleLruPage(0) {
  _contexts = NULL;

  // Can't put it in the LRU until it has been read in and made valid.
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayData::make_cow_copy
//       Access: Protected, Virtual
//  Description: Required to implement CopyOnWriteObject.
////////////////////////////////////////////////////////////////////
PT(CopyOnWriteObject) GeomVertexArrayData::
make_cow_copy() {
  return new GeomVertexArrayData(*this);
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayData::Constructor
//       Access: Published
//  Description: 
////////////////////////////////////////////////////////////////////
GeomVertexArrayData::
GeomVertexArrayData(const GeomVertexArrayFormat *array_format,
                    GeomVertexArrayData::UsageHint usage_hint) :
  SimpleLruPage(0),
  _array_format(array_format)
{
  OPEN_ITERATE_ALL_STAGES(_cycler) {
    CDStageWriter cdata(_cycler, pipeline_stage);
    cdata->_usage_hint = usage_hint;
  }
  CLOSE_ITERATE_ALL_STAGES(_cycler);

  _contexts = NULL;

  set_lru_size(0);
  nassertv(_array_format->is_registered());
}
  
////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayData::Copy Constructor
//       Access: Published
//  Description: 
////////////////////////////////////////////////////////////////////
GeomVertexArrayData::
GeomVertexArrayData(const GeomVertexArrayData &copy) :
  CopyOnWriteObject(copy),
  SimpleLruPage(copy),
  _array_format(copy._array_format),
  _cycler(copy._cycler)
{
  _contexts = NULL;

  copy.mark_used_lru();

  set_lru_size(get_data_size_bytes());
  nassertv(_array_format->is_registered());
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayData::Copy Assignment Operator
//       Access: Published
//  Description: The copy assignment operator is not pipeline-safe.
//               This will completely obliterate all stages of the
//               pipeline, so don't do it for a GeomVertexArrayData
//               that is actively being used for rendering.
////////////////////////////////////////////////////////////////////
void GeomVertexArrayData::
operator = (const GeomVertexArrayData &copy) {
  CopyOnWriteObject::operator = (copy);
  SimpleLruPage::operator = (copy);

  copy.mark_used_lru();

  _array_format = copy._array_format;
  _cycler = copy._cycler;

  OPEN_ITERATE_ALL_STAGES(_cycler) {
    CDStageWriter cdata(_cycler, pipeline_stage);
    cdata->_modified = Geom::get_next_modified();
  }
  CLOSE_ITERATE_ALL_STAGES(_cycler);

  nassertv(_array_format->is_registered());
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayData::Destructor
//       Access: Published, Virtual
//  Description: 
////////////////////////////////////////////////////////////////////
GeomVertexArrayData::
~GeomVertexArrayData() {
  release_all();
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayData::set_usage_hint
//       Access: Published
//  Description: Changes the UsageHint hint for this array.  See
//               get_usage_hint().
//
//               Don't call this in a downstream thread unless you
//               don't mind it blowing away other changes you might
//               have recently made in an upstream thread.
////////////////////////////////////////////////////////////////////
void GeomVertexArrayData::
set_usage_hint(GeomVertexArrayData::UsageHint usage_hint) {
  CDWriter cdata(_cycler, true);
  cdata->_usage_hint = usage_hint;
  cdata->_modified = Geom::get_next_modified();
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayData::output
//       Access: Published
//  Description: 
////////////////////////////////////////////////////////////////////
void GeomVertexArrayData::
output(ostream &out) const {
  out << get_num_rows() << " rows: " << *get_array_format();
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayData::write
//       Access: Published
//  Description: 
////////////////////////////////////////////////////////////////////
void GeomVertexArrayData::
write(ostream &out, int indent_level) const {
  _array_format->write_with_data(out, indent_level, this);
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayData::prepare
//       Access: Public
//  Description: Indicates that the data should be enqueued to be
//               prepared in the indicated prepared_objects at the
//               beginning of the next frame.  This will ensure the
//               data is already loaded into the GSG if it is expected
//               to be rendered soon.
//
//               Use this function instead of prepare_now() to preload
//               datas from a user interface standpoint.
////////////////////////////////////////////////////////////////////
void GeomVertexArrayData::
prepare(PreparedGraphicsObjects *prepared_objects) {
  prepared_objects->enqueue_vertex_buffer(this);
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayData::is_prepared
//       Access: Published
//  Description: Returns true if the data has already been prepared
//               or enqueued for preparation on the indicated GSG,
//               false otherwise.
////////////////////////////////////////////////////////////////////
bool GeomVertexArrayData::
is_prepared(PreparedGraphicsObjects *prepared_objects) const {
  if (_contexts == (Contexts *)NULL) {
    return false;
  }
  Contexts::const_iterator ci;
  ci = _contexts->find(prepared_objects);
  if (ci != _contexts->end()) {
    return true;
  }
  return prepared_objects->is_vertex_buffer_queued(this);
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayData::prepare_now
//       Access: Public
//  Description: Creates a context for the data on the particular
//               GSG, if it does not already exist.  Returns the new
//               (or old) VertexBufferContext.  This assumes that the
//               GraphicsStateGuardian is the currently active
//               rendering context and that it is ready to accept new
//               datas.  If this is not necessarily the case, you
//               should use prepare() instead.
//
//               Normally, this is not called directly except by the
//               GraphicsStateGuardian; a data does not need to be
//               explicitly prepared by the user before it may be
//               rendered.
////////////////////////////////////////////////////////////////////
VertexBufferContext *GeomVertexArrayData::
prepare_now(PreparedGraphicsObjects *prepared_objects, 
            GraphicsStateGuardianBase *gsg) {
  if (_contexts == (Contexts *)NULL) {
    _contexts = new Contexts;
  }
  Contexts::const_iterator ci;
  ci = _contexts->find(prepared_objects);
  if (ci != _contexts->end()) {
    return (*ci).second;
  }

  VertexBufferContext *vbc = prepared_objects->prepare_vertex_buffer_now(this, gsg);
  if (vbc != (VertexBufferContext *)NULL) {
    (*_contexts)[prepared_objects] = vbc;
  }
  return vbc;
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayData::release
//       Access: Public
//  Description: Frees the data context only on the indicated object,
//               if it exists there.  Returns true if it was released,
//               false if it had not been prepared.
////////////////////////////////////////////////////////////////////
bool GeomVertexArrayData::
release(PreparedGraphicsObjects *prepared_objects) {
  if (_contexts != (Contexts *)NULL) {
    Contexts::iterator ci;
    ci = _contexts->find(prepared_objects);
    if (ci != _contexts->end()) {
      VertexBufferContext *vbc = (*ci).second;
      prepared_objects->release_vertex_buffer(vbc);
      return true;
    }
  }

  // Maybe it wasn't prepared yet, but it's about to be.
  return prepared_objects->dequeue_vertex_buffer(this);
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayData::release_all
//       Access: Public
//  Description: Frees the context allocated on all objects for which
//               the data has been declared.  Returns the number of
//               contexts which have been freed.
////////////////////////////////////////////////////////////////////
int GeomVertexArrayData::
release_all() {
  int num_freed = 0;

  if (_contexts != (Contexts *)NULL) {
    // We have to traverse a copy of the _contexts list, because the
    // PreparedGraphicsObjects object will call clear_prepared() in response
    // to each release_vertex_buffer(), and we don't want to be modifying the
    // _contexts list while we're traversing it.
    Contexts temp = *_contexts;
    num_freed = (int)_contexts->size();
    
    Contexts::const_iterator ci;
    for (ci = temp.begin(); ci != temp.end(); ++ci) {
      PreparedGraphicsObjects *prepared_objects = (*ci).first;
      VertexBufferContext *vbc = (*ci).second;
      prepared_objects->release_vertex_buffer(vbc);
    }
    
    // Now that we've called release_vertex_buffer() on every known context,
    // the _contexts list should have completely emptied itself.
    nassertr(_contexts == NULL, num_freed);
  }

  return num_freed;
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayData::lru_epoch
//       Access: Published, Static
//  Description: Marks that an epoch has passed in each LRU.  Asks the
//               LRU's to consider whether they should perform
//               evictions.
////////////////////////////////////////////////////////////////////
void GeomVertexArrayData::
lru_epoch() {
  _independent_lru.begin_epoch();
  VertexDataPage::get_global_lru(VertexDataPage::RC_resident)->begin_epoch();
  VertexDataPage::get_global_lru(VertexDataPage::RC_compressed)->begin_epoch();
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayData::evict_lru
//       Access: Public, Virtual
//  Description: Evicts the page from the LRU.  Called internally when
//               the LRU determines that it is full.  May also be
//               called externally when necessary to explicitly evict
//               the page.
//
//               It is legal for this method to either evict the page
//               as requested, do nothing (in which case the eviction
//               will be requested again at the next epoch), or
//               requeue itself on the tail of the queue (in which
//               case the eviction will be requested again much
//               later).
////////////////////////////////////////////////////////////////////
void GeomVertexArrayData::
evict_lru() {
  dequeue_lru();
  CDWriter cdata(_cycler, true);
  cdata->_buffer.page_out(_book);
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayData::clear_prepared
//       Access: Private
//  Description: Removes the indicated PreparedGraphicsObjects table
//               from the data array's table, without actually
//               releasing the data array.  This is intended to be
//               called only from
//               PreparedGraphicsObjects::release_vertex_buffer(); it should
//               never be called by user code.
////////////////////////////////////////////////////////////////////
void GeomVertexArrayData::
clear_prepared(PreparedGraphicsObjects *prepared_objects) {
  nassertv(_contexts != (Contexts *)NULL);

  Contexts::iterator ci;
  ci = _contexts->find(prepared_objects);
  if (ci != _contexts->end()) {
    _contexts->erase(ci);
    if (_contexts->empty()) {
      delete _contexts;
      _contexts = NULL;
    }
  } else {
    // If this assertion fails, clear_prepared() was given a
    // prepared_objects which the data array didn't know about.
    nassertv(false);
  }
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayData::reverse_data_endianness
//       Access: Private
//  Description: Fills a new data array with all numeric values
//               expressed in the indicated array reversed,
//               byte-for-byte, to convert littleendian to bigendian
//               and vice-versa.
////////////////////////////////////////////////////////////////////
void GeomVertexArrayData::
reverse_data_endianness(unsigned char *dest, const unsigned char *source, 
                        size_t size) {
  int num_columns = _array_format->get_num_columns();

  // Walk through each row of the data.
  for (size_t pi = 0; pi < size; pi += _array_format->get_stride()) {
    // For each row, visit all of the columns; and for each column,
    // visit all of the components of that column.
    for (int ci = 0; ci < num_columns; ++ci) {
      const GeomVertexColumn *col = _array_format->get_column(ci);
      int component_bytes = col->get_component_bytes();
      if (component_bytes > 1) {
        // Get the index of the beginning of the column.
        size_t ci = pi + col->get_start();

        int num_components = col->get_num_components();
        for (int cj = 0; cj < num_components; ++cj) {
          // Reverse the bytes of each component.
          ReversedNumericData nd(source + ci, component_bytes);
          nd.store_value(dest + ci, component_bytes);
          ci += component_bytes;
        }
      }
    }
  }
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayData::register_with_read_factory
//       Access: Public, Static
//  Description: Tells the BamReader how to create objects of type
//               GeomVertexArrayData.
////////////////////////////////////////////////////////////////////
void GeomVertexArrayData::
register_with_read_factory() {
  BamReader::get_factory()->register_factory(get_class_type(), make_from_bam);
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayData::write_datagram
//       Access: Public, Virtual
//  Description: Writes the contents of this object to the datagram
//               for shipping out to a Bam file.
////////////////////////////////////////////////////////////////////
void GeomVertexArrayData::
write_datagram(BamWriter *manager, Datagram &dg) {
  CopyOnWriteObject::write_datagram(manager, dg);

  manager->write_pointer(dg, _array_format);
  manager->write_cdata(dg, _cycler, this);
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayData::read_raw_data
//       Access: Public
//  Description: Called by CData::fillin to read the raw data
//               of the array from the indicated datagram.
////////////////////////////////////////////////////////////////////
PTA_uchar GeomVertexArrayData::
read_raw_data(BamReader *manager, DatagramIterator &scan) {
  size_t size = scan.get_uint32();
  PTA_uchar data = PTA_uchar::empty_array(size, get_class_type());
  const unsigned char *source_data = 
    (const unsigned char *)scan.get_datagram().get_data();
  memcpy(data, source_data + scan.get_current_index(), size);
  scan.skip_bytes(size);

  return data;
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayData::complete_pointers
//       Access: Public, Virtual
//  Description: Receives an array of pointers, one for each time
//               manager->read_pointer() was called in fillin().
//               Returns the number of pointers processed.
////////////////////////////////////////////////////////////////////
int GeomVertexArrayData::
complete_pointers(TypedWritable **p_list, BamReader *manager) {
  int pi = CopyOnWriteObject::complete_pointers(p_list, manager);

  _array_format = DCAST(GeomVertexArrayFormat, p_list[pi++]);

  return pi;
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayData::finalize
//       Access: Public, Virtual
//  Description: Called by the BamReader to perform any final actions
//               needed for setting up the object after all objects
//               have been read and all pointers have been completed.
////////////////////////////////////////////////////////////////////
void GeomVertexArrayData::
finalize(BamReader *manager) {
  // Now we need to register the format that we have read from the bam
  // file (since it doesn't come out of the bam file automatically
  // registered).  This may change the format's pointer, which we
  // should then update our own data to reflect.  But since this may
  // cause the unregistered object to destruct, we have to also tell
  // the BamReader to return the new object from now on.

  CDWriter cdata(_cycler, true);

  CPT(GeomVertexArrayFormat) new_array_format = 
    GeomVertexArrayFormat::register_format(_array_format);

  manager->change_pointer(_array_format, new_array_format);
  _array_format = new_array_format;

  PT(BamAuxData) aux_data = (BamAuxData *)manager->get_aux_data(this, "");
  if (aux_data != (BamAuxData *)NULL) {
    if (aux_data->_endian_reversed) {
      // Now is the time to endian-reverse the data.
      VertexDataBuffer new_buffer(cdata->_buffer.get_size());
      reverse_data_endianness(new_buffer.get_write_pointer(), cdata->_buffer.get_read_pointer(true), cdata->_buffer.get_size());
      cdata->_buffer.swap(new_buffer);
    }
  }

  set_lru_size(cdata->_buffer.get_size());
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayData::make_from_bam
//       Access: Protected, Static
//  Description: This function is called by the BamReader's factory
//               when a new object of type GeomVertexArrayData is encountered
//               in the Bam file.  It should create the GeomVertexArrayData
//               and extract its information from the file.
////////////////////////////////////////////////////////////////////
TypedWritable *GeomVertexArrayData::
make_from_bam(const FactoryParams &params) {
  GeomVertexArrayData *object = new GeomVertexArrayData;
  DatagramIterator scan;
  BamReader *manager;

  parse_params(params, scan, manager);
  object->fillin(scan, manager);
  manager->register_finalize(object);

  return object;
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayData::fillin
//       Access: Protected
//  Description: This internal function is called by make_from_bam to
//               read in all of the relevant data from the BamFile for
//               the new GeomVertexArrayData.
////////////////////////////////////////////////////////////////////
void GeomVertexArrayData::
fillin(DatagramIterator &scan, BamReader *manager) {
  CopyOnWriteObject::fillin(scan, manager);

  manager->read_pointer(scan);
  manager->read_cdata(scan, _cycler, this);
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayData::CData::Destructor
//       Access: Public, Virtual
//  Description:
////////////////////////////////////////////////////////////////////
GeomVertexArrayData::CData::
~CData() {
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayData::CData::make_copy
//       Access: Public, Virtual
//  Description:
////////////////////////////////////////////////////////////////////
CycleData *GeomVertexArrayData::CData::
make_copy() const {
  return new CData(*this);
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayData::CData::write_datagram
//       Access: Public, Virtual
//  Description: Writes the contents of this object to the datagram
//               for shipping out to a Bam file.
////////////////////////////////////////////////////////////////////
void GeomVertexArrayData::CData::
write_datagram(BamWriter *manager, Datagram &dg, void *extra_data) const {
  GeomVertexArrayData *array_data = (GeomVertexArrayData *)extra_data;
  dg.add_uint8(_usage_hint);

  dg.add_uint32(_buffer.get_size());

  if (manager->get_file_endian() == BE_native) {
    // For native endianness, we only have to write the data directly.
    dg.append_data(_buffer.get_read_pointer(true), _buffer.get_size());

  } else {
    // Otherwise, we have to convert it.
    unsigned char *new_data = (unsigned char *)alloca(_buffer.get_size());
    array_data->reverse_data_endianness(new_data, _buffer.get_read_pointer(true), _buffer.get_size());
    dg.append_data(new_data, _buffer.get_size());
  }
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayData::CData::fillin
//       Access: Public, Virtual
//  Description: This internal function is called by make_from_bam to
//               read in all of the relevant data from the BamFile for
//               the new GeomVertexArrayData.
////////////////////////////////////////////////////////////////////
void GeomVertexArrayData::CData::
fillin(DatagramIterator &scan, BamReader *manager, void *extra_data) {
  GeomVertexArrayData *array_data = (GeomVertexArrayData *)extra_data;
  _usage_hint = (UsageHint)scan.get_uint8();

  if (manager->get_file_minor_ver() < 8) {
    // Before bam version 6.8, the array data was a PTA_uchar.
    PTA_uchar new_data;
    READ_PTA(manager, scan, array_data->read_raw_data, new_data);
    _buffer.unclean_realloc(new_data.size());
    memcpy(_buffer.get_write_pointer(), &new_data[0], new_data.size());

  } else {
    // Now, the array data is just stored directly.
    size_t size = scan.get_uint32();
    _buffer.unclean_realloc(size);

    const unsigned char *source_data = 
      (const unsigned char *)scan.get_datagram().get_data();
    memcpy(_buffer.get_write_pointer(), source_data + scan.get_current_index(), size);
    scan.skip_bytes(size);
  }

  bool endian_reversed = false;

  if (manager->get_file_endian() != BE_native) {
    // For non-native endian files, we have to convert the data.  

    if (array_data->_array_format == (GeomVertexArrayFormat *)NULL) {
      // But we can't do that until we've completed the _array_format
      // pointer, which tells us how to convert it.
      endian_reversed = true;
    } else {
      // Since we have the _array_format pointer now, we can reverse
      // it immediately (and we should, to support threaded CData
      // updates).
      VertexDataBuffer new_buffer(_buffer.get_size());
      array_data->reverse_data_endianness(new_buffer.get_write_pointer(), _buffer.get_read_pointer(true), _buffer.get_size());
      _buffer.swap(new_buffer);
    }
  }

  if (endian_reversed) {
    PT(BamAuxData) aux_data = new BamAuxData;
    aux_data->_endian_reversed = endian_reversed;
    manager->set_aux_data(array_data, "", aux_data);
  }

  array_data->set_lru_size(_buffer.get_size());

  _modified = Geom::get_next_modified();
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayDataHandle::get_write_pointer
//       Access: Public
//  Description: Returns a writable pointer to the beginning of the
//               actual data stream.
////////////////////////////////////////////////////////////////////
unsigned char *GeomVertexArrayDataHandle::
get_write_pointer() {
  nassertr(_writable, NULL);
  mark_used();
  _cdata->_modified = Geom::get_next_modified();
  return _cdata->_buffer.get_write_pointer();
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayDataHandle::set_num_rows
//       Access: Public
//  Description: 
////////////////////////////////////////////////////////////////////
bool GeomVertexArrayDataHandle::
set_num_rows(int n) {
  nassertr(_writable, false);
  mark_used();

  int stride = _object->_array_format->get_stride();
  size_t new_size = n * stride;
  size_t orig_size = _cdata->_buffer.get_size();

  if (new_size != orig_size) {
    _cdata->_buffer.clean_realloc(new_size);

    // Now ensure that the newly-added rows are initialized to 0.
    if (new_size > orig_size) {
      memset(_cdata->_buffer.get_write_pointer() + orig_size, 0,
             new_size - orig_size);
    }

    _cdata->_modified = Geom::get_next_modified();

    if (get_current_thread()->get_pipeline_stage() == 0) {
      _object->set_lru_size(_cdata->_buffer.get_size());
    }
    return true;
  }
  
  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayDataHandle::unclean_set_num_rows
//       Access: Public
//  Description: 
////////////////////////////////////////////////////////////////////
bool GeomVertexArrayDataHandle::
unclean_set_num_rows(int n) {
  nassertr(_writable, false);
  mark_used();

  int stride = _object->_array_format->get_stride();
  size_t new_size = n * stride;
  size_t orig_size = _cdata->_buffer.get_size();

  if (new_size != orig_size) {
    _cdata->_buffer.unclean_realloc(new_size);
    // No need to fill to zero or copy the old buffer, since this is
    // unclean_set_num_rows().

    _cdata->_modified = Geom::get_next_modified();

    if (get_current_thread()->get_pipeline_stage() == 0) {
      _object->set_lru_size(_cdata->_buffer.get_size());
    }
    return true;
  }
  
  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayDataHandle::copy_data_from
//       Access: Public
//  Description: Copies the entire data array from the other object.
////////////////////////////////////////////////////////////////////
void GeomVertexArrayDataHandle::
copy_data_from(const GeomVertexArrayDataHandle *other) {
  nassertv(_writable);
  mark_used();
  other->mark_used();

  _cdata->_buffer.unclean_realloc(other->_cdata->_buffer.get_size());

  unsigned char *dest = _cdata->_buffer.get_write_pointer();
  const unsigned char *source = other->_cdata->_buffer.get_read_pointer(true);
  size_t size = other->_cdata->_buffer.get_size();
  memcpy(dest, source, size);

  _cdata->_modified = Geom::get_next_modified();

  if (get_current_thread()->get_pipeline_stage() == 0) {
    _object->set_lru_size(_cdata->_buffer.get_size());
  }
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayDataHandle::copy_subdata_from
//       Access: Public
//  Description: Copies a portion of the data array from the other
//               object into a portion of the data array of this
//               object.  If to_size != from_size, the size of this
//               data array is adjusted accordingly.
////////////////////////////////////////////////////////////////////
void GeomVertexArrayDataHandle::
copy_subdata_from(size_t to_start, size_t to_size,
                  const GeomVertexArrayDataHandle *other,
                  size_t from_start, size_t from_size) {
  nassertv(_writable);
  mark_used();
  other->mark_used();

  VertexDataBuffer &to_buffer = _cdata->_buffer;
  size_t to_buffer_orig_size = to_buffer.get_size();
  to_start = min(to_start, to_buffer_orig_size);
  to_size = min(to_size, to_buffer_orig_size - to_start);

  const VertexDataBuffer &from_buffer = other->_cdata->_buffer;
  size_t from_buffer_orig_size = from_buffer.get_size();
  from_start = min(from_start, from_buffer_orig_size);
  from_size = min(from_size, from_buffer_orig_size - from_start);

  if (from_size < to_size) {
    // Reduce the array.
    unsigned char *pointer = to_buffer.get_write_pointer();
    memmove(pointer + to_start + to_size, 
            pointer + to_start + from_size,
            to_buffer_orig_size - (to_start + to_size));
    to_buffer.clean_realloc(to_buffer_orig_size + from_size - to_size);

  } else if (to_size < from_size) {
    // Expand the array.
    to_buffer.clean_realloc(to_buffer_orig_size + from_size - to_size);
    unsigned char *pointer = to_buffer.get_write_pointer();
    memmove(pointer + to_start + to_size, 
            pointer + to_start + from_size,
            to_buffer_orig_size - (to_start + to_size));
  }

  // Now copy the data.
  memcpy(to_buffer.get_write_pointer() + to_start, 
         other->get_read_pointer(true) + from_start, 
         from_size);
  _cdata->_modified = Geom::get_next_modified();

  if (get_current_thread()->get_pipeline_stage() == 0) {
    _object->set_lru_size(_cdata->_buffer.get_size());
  }
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayDataHandle::set_data
//       Access: Public
//  Description: Replaces the entire raw data array with the contents
//               of the indicated string.  This is primarily for the
//               benefit of high-level languages like Python.
////////////////////////////////////////////////////////////////////
void GeomVertexArrayDataHandle::
set_data(const string &data) {
  nassertv(_writable);
  mark_used();

  _cdata->_buffer.unclean_realloc(data.size());
  memcpy(_cdata->_buffer.get_write_pointer(), data.data(), data.size());

  _cdata->_modified = Geom::get_next_modified();

  if (get_current_thread()->get_pipeline_stage() == 0) {
    _object->set_lru_size(_cdata->_buffer.get_size());
  }
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayDataHandle::copy_subdata_from
//       Access: Public
//  Description: Replaces a portion of the data array from the
//               indicated string.  If size != data.size(), the size
//               of this data array is adjusted accordingly.
//
//               This is primarily for the benefit of high-level
//               languages like Python.
////////////////////////////////////////////////////////////////////
void GeomVertexArrayDataHandle::
set_subdata(size_t start, size_t size, const string &data) {
  nassertv(_writable);
  mark_used();

  VertexDataBuffer &to_buffer = _cdata->_buffer;
  size_t to_buffer_orig_size = to_buffer.get_size();
  start = min(start, to_buffer_orig_size);
  size = min(size, to_buffer_orig_size - start);
  
  size_t from_size = data.size();

  if (from_size < size) {
    // Reduce the array.
    unsigned char *pointer = to_buffer.get_write_pointer();
    memmove(pointer + start + size, 
            pointer + start + from_size,
            to_buffer_orig_size - (start + size));
    to_buffer.clean_realloc(to_buffer_orig_size + from_size - size);

  } else if (size < from_size) {
    // Expand the array.
    to_buffer.clean_realloc(to_buffer_orig_size + from_size - size);
    unsigned char *pointer = to_buffer.get_write_pointer();
    memmove(pointer + start + size, 
            pointer + start + from_size,
            to_buffer_orig_size - (start + size));
  }

  // Now copy the data.
  memcpy(to_buffer.get_write_pointer() + start, data.data(), from_size);
  _cdata->_modified = Geom::get_next_modified();

  if (get_current_thread()->get_pipeline_stage() == 0) {
    _object->set_lru_size(_cdata->_buffer.get_size());
  }
}
