Initial commit
This commit is contained in:
commit
95719e7a36
2 changed files with 218 additions and 0 deletions
139
list.odin
Normal file
139
list.odin
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
package list
|
||||
|
||||
IntrusiveList :: struct {
|
||||
head: ^IntrusiveListNode
|
||||
}
|
||||
|
||||
IntrusiveListNode :: struct {
|
||||
prev, next: ^IntrusiveListNode
|
||||
}
|
||||
|
||||
IntrusiveListIterator :: struct($T: typeid) {
|
||||
curr: ^IntrusiveListNode,
|
||||
offset: uintptr,
|
||||
}
|
||||
|
||||
intrusive_list_push_front :: proc "contextless" (list: ^IntrusiveList, node: ^IntrusiveListNode) {
|
||||
if list.head == nil {
|
||||
list.head = node
|
||||
node.next, node.prev = node, node
|
||||
} else {
|
||||
tail := list.head.prev
|
||||
list.head.prev = node
|
||||
tail.next = node
|
||||
node.next, node.prev = list.head, tail
|
||||
list.head = node
|
||||
}
|
||||
}
|
||||
|
||||
intrusive_list_push_back :: proc (list: ^IntrusiveList, node: ^IntrusiveListNode) {
|
||||
if list.head == nil {
|
||||
list.head = node
|
||||
node.next, node.prev = node, node
|
||||
} else {
|
||||
node.next, node.prev = list.head, list.head.prev
|
||||
list.head.prev.next = node
|
||||
list.head.prev = node
|
||||
}
|
||||
}
|
||||
|
||||
intrusive_list_pop_front :: proc "contextless" (list: ^IntrusiveList) -> ^IntrusiveListNode {
|
||||
head := list.head
|
||||
if head == nil {
|
||||
return head
|
||||
} else if intrusive_list_node_is_only(list^, head) {
|
||||
list.head = nil
|
||||
return head
|
||||
}
|
||||
head.prev.next = head.next
|
||||
head.next.prev = head.prev
|
||||
list.head = head.next
|
||||
return head
|
||||
}
|
||||
|
||||
intrusive_list_pop_back :: proc "contextless" (list: ^IntrusiveList) -> ^IntrusiveListNode {
|
||||
head := list.head
|
||||
if head == nil {
|
||||
return nil
|
||||
}
|
||||
if intrusive_list_node_is_only(list^, head) {
|
||||
list.head = nil
|
||||
return head
|
||||
}
|
||||
tail := list.head.prev
|
||||
tail.prev.next = tail.next
|
||||
tail.next.prev = tail.prev
|
||||
return tail
|
||||
}
|
||||
|
||||
intrusive_list_node_is_only :: proc "contextless" (list: IntrusiveList, node: ^IntrusiveListNode) -> bool {
|
||||
return node == list.head && node == node.next && node == node.prev
|
||||
}
|
||||
|
||||
intrusive_list_remove :: proc "contextless" (list: ^IntrusiveList, node: ^IntrusiveListNode) {
|
||||
if intrusive_list_node_is_only(list^, node) {
|
||||
list.head = nil
|
||||
} else {
|
||||
prev := node.prev
|
||||
next := node.next
|
||||
prev.next = next
|
||||
next.prev = prev
|
||||
}
|
||||
}
|
||||
|
||||
intrusive_list_get_next :: proc "contextless" (node: ^IntrusiveListNode, $T: typeid, $field_name: string) -> ^T
|
||||
where intrinsics.type_has_field(T, field_name),
|
||||
intrinsics.type_field_type(T, field_name) == IntrusiveListNode {
|
||||
return (^T)(uintptr(node.next) - offset_of_by_string(T, field_name))
|
||||
}
|
||||
|
||||
intrusive_list_get_previous :: proc "contextless" (node: ^IntrusiveListNode, $T: typeid, $field_name: string) -> ^T
|
||||
where intrinsics.type_has_field(T, field_name),
|
||||
intrinsics.type_field_type(T, field_name) == IntrusiveListNode {
|
||||
return (^T)(uintptr(node.prev) - offset_of_by_string(T, field_name))
|
||||
}
|
||||
|
||||
intrusive_list_get_relative :: proc "contextless" (node: ^IntrusiveListNode, $T: typeid, $field_name: string, pos: int) -> ^T {
|
||||
it := intrusive_list_iterator_from_node(node, T, field_name)
|
||||
switch math.sign(pos) {
|
||||
case 0:
|
||||
return (^T)(uintptr(it.curr) - it.offset)
|
||||
case 1:
|
||||
node: ^T
|
||||
for i in 0..<pos {
|
||||
node = intrusive_list_iterate_next(&it)
|
||||
}
|
||||
return node
|
||||
}
|
||||
node: ^T
|
||||
for i in 0..<abs(pos) {
|
||||
node = intrusive_list_iterate_prev(&it)
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
intrusive_list_is_empty :: proc "contextless" (list: IntrusiveList) -> bool {
|
||||
return list.head == nil
|
||||
}
|
||||
|
||||
intrusive_list_iterator_head :: proc "contextless" (list: IntrusiveList, $T: typeid, $field_name: string) -> IntrusiveListIterator(T)
|
||||
where intrinsics.type_has_field(T, field_name),
|
||||
intrinsics.type_field_type(T, field_name) == IntrusiveListNode {
|
||||
return {list.head, offset_of_by_string(T, field_name)}
|
||||
}
|
||||
|
||||
intrusive_list_iterator_from_node :: proc "contextless" (node: ^IntrusiveListNode, $T: typeid, $field_name: string) -> IntrusiveListIterator(T)
|
||||
where intrinsics.type_has_field(T, field_name),
|
||||
intrinsics.type_field_type(T, field_name) == IntrusiveListNode {
|
||||
return {node, offset_of_by_string(T, field_name)}
|
||||
}
|
||||
|
||||
intrusive_list_iterate_next :: proc "contextless" (it: ^IntrusiveListIterator($T)) -> ^T {
|
||||
it.curr = it.curr.next
|
||||
return (^T)(uintptr(it.curr) - it.offset)
|
||||
}
|
||||
|
||||
intrusive_list_iterate_prev :: proc "contextless" (it: ^IntrusiveListIterator($T)) -> ^T {
|
||||
it.curr = it.curr.prev
|
||||
return (^T)(uintptr(it.curr) - it.offset)
|
||||
}
|
||||
79
tests/tests.odin
Normal file
79
tests/tests.odin
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
package tests
|
||||
|
||||
import "core:testing"
|
||||
import list "../"
|
||||
|
||||
Entity :: struct {
|
||||
id: int,
|
||||
using node: list.IntrusiveListNode,
|
||||
position: [2]f32,
|
||||
}
|
||||
|
||||
IntrusiveListTestData :: struct {
|
||||
nodes: [3]Entity,
|
||||
list: list.IntrusiveList,
|
||||
first, second, last: ^list.IntrusiveListNode,
|
||||
}
|
||||
|
||||
intrusive_list_setup_data :: proc(data: ^IntrusiveListTestData) {
|
||||
for i in 0..<len(data.nodes) {
|
||||
data.nodes[i].id = i
|
||||
}
|
||||
list.intrusive_list_push_back(&data.list, &data.nodes[0])
|
||||
list.intrusive_list_push_back(&data.list, &data.nodes[1])
|
||||
list.intrusive_list_push_back(&data.list, &data.nodes[2])
|
||||
data.first, data.second, data.last = &data.nodes[0], &data.nodes[1], &data.nodes[2]
|
||||
}
|
||||
|
||||
@test intrusive_list_push_back_loops_forward :: proc(_: ^testing.T) {
|
||||
data: IntrusiveListTestData
|
||||
intrusive_list_setup_data(&data)
|
||||
assert(data.second == data.second)
|
||||
}
|
||||
|
||||
@test intrusive_list_push_back_loops_backward :: proc(_: ^testing.T) {
|
||||
data: IntrusiveListTestData
|
||||
intrusive_list_setup_data(&data)
|
||||
assert(data.second == data.second)
|
||||
}
|
||||
|
||||
@test intrusive_list_pop_back_loops_forward :: proc(_: ^testing.T) {
|
||||
data: IntrusiveListTestData
|
||||
intrusive_list_setup_data(&data)
|
||||
list.intrusive_list_pop_back(&data.list)
|
||||
list.intrusive_list_pop_back(&data.list)
|
||||
assert(data.list.head.next == data.list.head)
|
||||
}
|
||||
|
||||
@test intrusive_list_pop_back_loops_backward :: proc(_: ^testing.T) {
|
||||
data: IntrusiveListTestData
|
||||
intrusive_list_setup_data(&data)
|
||||
list.intrusive_list_pop_back(&data.list)
|
||||
list.intrusive_list_pop_back(&data.list)
|
||||
assert(data.list.head.next == data.list.head)
|
||||
}
|
||||
|
||||
@test intrusive_list_remove_links :: proc(_: ^testing.T) {
|
||||
data: IntrusiveListTestData
|
||||
intrusive_list_setup_data(&data)
|
||||
list.intrusive_list_remove(&data.list, data.second)
|
||||
assert(data.list.head != nil)
|
||||
assert(data.list.head == data.first)
|
||||
assert(data.list.head.next == data.last)
|
||||
}
|
||||
|
||||
@test intrusive_list_iterator_next_works :: proc(_: ^testing.T) {
|
||||
data: IntrusiveListTestData
|
||||
intrusive_list_setup_data(&data)
|
||||
it := list.intrusive_list_iterator_head(data.list, Entity, "node")
|
||||
entity := list.intrusive_list_iterate_next(&it)
|
||||
assert(entity.id == 1)
|
||||
}
|
||||
|
||||
@test intrusive_list_iterator_next_from_node_works :: proc(_: ^testing.T) {
|
||||
data: IntrusiveListTestData
|
||||
intrusive_list_setup_data(&data)
|
||||
it := list.intrusive_list_iterator_from_node(data.second, Entity, "node")
|
||||
entity := list.intrusive_list_iterate_next(&it)
|
||||
assert(entity.id == 2)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue