aboutsummaryrefslogtreecommitdiff
path: root/src/rt/circular_buffer.cpp
blob: 415a966b8fd1eaa1ddf74b5f80206fef8de5d413 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
/*
 * A simple resizable circular buffer.
 */

#include "rust_internal.h"

circular_buffer::circular_buffer(rust_dom *dom, size_t unit_sz) :
    dom(dom),
    _buffer_sz(INITIAL_CIRCULAR_BUFFFER_SIZE_IN_UNITS * unit_sz),
    _unit_sz(unit_sz),
    _next(0),
    _unread(0),
    _buffer((uint8_t *)dom->calloc(_buffer_sz)) {

    A(dom, unit_sz, "Unit size must be larger than zero.");

    dom->log(rust_log::MEM | rust_log::COMM,
             "new circular_buffer(buffer_sz=%d, unread=%d)"
             "-> circular_buffer=0x%" PRIxPTR,
             _buffer_sz, _unread, this);

    A(dom, _buffer, "Failed to allocate buffer.");
}

circular_buffer::~circular_buffer() {
    dom->log(rust_log::MEM | rust_log::COMM,
             "~circular_buffer 0x%" PRIxPTR,
             this);
    I(dom, _buffer);
    // I(dom, _unread == 0);
    dom->free(_buffer);
}

/**
 * Copies the unread data from this buffer to the "dst" address.
 */
void
circular_buffer::transfer(void *dst) {
    I(dom, dst);
    uint8_t *ptr = (uint8_t *) dst;
    for (size_t i = 0; i < _unread; i += _unit_sz) {
        memcpy(&ptr[i], &_buffer[(_next + i) % _buffer_sz], _unit_sz);
    }
}

/**
 * Copies the data at the "src" address into this buffer. The buffer is
 * grown if it isn't large enough.
 */
void
circular_buffer::enqueue(void *src) {
    I(dom, src);
    I(dom, _unread <= _buffer_sz);

    // Grow if necessary.
    if (_unread == _buffer_sz) {
        I(dom, _buffer_sz <= MAX_CIRCULAR_BUFFFER_SIZE);
        void *tmp = dom->malloc(_buffer_sz << 1);
        transfer(tmp);
        _buffer_sz <<= 1;
        dom->free(_buffer);
        _buffer = (uint8_t *)tmp;
    }

    dom->log(rust_log::MEM | rust_log::COMM,
             "circular_buffer enqueue "
             "unread: %d, buffer_sz: %d, unit_sz: %d",
             _unread, _buffer_sz, _unit_sz);

    I(dom, _unread < _buffer_sz);
    I(dom, _unread + _unit_sz <= _buffer_sz);

    // Copy data
    size_t i = (_next + _unread) % _buffer_sz;
    memcpy(&_buffer[i], src, _unit_sz);
    _unread += _unit_sz;

    dom->log(rust_log::MEM | rust_log::COMM,
             "circular_buffer pushed data at index: %d", i);
}

/**
 * Copies data from this buffer to the "dst" address. The buffer is
 * shrunk if possible.
 */
void
circular_buffer::dequeue(void *dst) {
    I(dom, dst);
    I(dom, _unit_sz > 0);
    I(dom, _unread >= _unit_sz);
    I(dom, _unread <= _buffer_sz);
    I(dom, _buffer);

    memcpy(dst, &_buffer[_next], _unit_sz);
    dom->log(rust_log::MEM | rust_log::COMM,
             "shifted data from index %d", _next);
    _unread -= _unit_sz;
    _next += _unit_sz;
    I(dom, _next <= _buffer_sz);
    if (_next == _buffer_sz) {
        _next = 0;
    }

    // Shrink if possible.
    if (_buffer_sz >= INITIAL_CIRCULAR_BUFFFER_SIZE_IN_UNITS * _unit_sz &&
        _unread <= _buffer_sz / 4) {
        dom->log(rust_log::MEM | rust_log::COMM,
                 "circular_buffer is shrinking to %d bytes", _buffer_sz / 2);
        void *tmp = dom->malloc(_buffer_sz / 2);
        transfer(tmp);
        _buffer_sz >>= 1;
        dom->free(_buffer);
        _buffer = (uint8_t *)tmp;
        _next = 0;
    }

}

bool
circular_buffer::is_empty() {
    return _unread == 0;
}