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
//------------------------------------------------------------------------------
// Luke Titley : from+usd_rs@luketitley.com
//------------------------------------------------------------------------------

use crate::pxr;

use cpp::*;
use std::ffi::CStr;

cpp! {{
    #pragma GCC diagnostic push
    #pragma GCC diagnostic ignored "-Wunused-parameter"
    #pragma GCC diagnostic ignored "-Wmissing-field-initializers"
    #include "pxr/base/tf/token.h"
    #pragma GCC diagnostic pop
}}

cpp_class!(
    /// Token for efficient comparison, assignment, and hashing of known strings.
    ///
    /// A Token is a handle for a registered string, and can be compared,
    /// assigned, and hashed in constant time. It is useful when a bounded number of
    /// strings are used as fixed symbols (but never modified).
    ///
    /// For example, the set of avar names in a shot is large but bounded, and once
    /// an avar name is discovered, it is never manipulated. If these names were
    /// passed around as strings, every comparison and hash would be linear in the
    /// number of characters. (String assignment itself is sometimes a constant time
    /// operation, but it is sometimes linear in the length of the string as well as
    /// requiring a memory allocation.)
    ///
    /// To use Token, simply create an instance from a string or const char*. If
    /// the string hasn't been seen before, a copy of it is added to a global table.
    /// The resulting Token is simply a wrapper around an string*, pointing that
    /// canonical copy of the string. Thus, operations on the token are very fast.
    /// (The string's hash is simply the address of the canonical copy, so hashing
    /// the string is constant time.)
    ///
    /// Note: Access to the global table is protected by a mutex. This is a good
    /// idea as long as clients do not construct tokens from strings too frequently.
    /// Construct tokens only as often as you must (for example, as you read data
    /// files), and never in inner loops. Of course, once you have a token, feel
    /// free to compare, assign, and hash it as often as you like. (That's what it's
    /// for.) In order to help prevent tokens from being re-created over and over,
    /// auto type conversion from string and char* to Token is disabled (you must
    /// use the explicit Token constructors). However, auto conversion from
    /// Token to string and char* is provided.
    pub unsafe struct Token as "pxr::TfToken"
);

impl Token {
    /// Return the text that this token represents.
    pub fn get_text(&self) -> pxr::Result<&str> {
        let text = unsafe {
            std::ffi::CStr::from_ptr(cpp!([self as "const pxr::TfToken *"]
                    -> * const std::os::raw::c_char as "const char *" {
                return self->GetText();
            }))
        };

        Ok(text.to_str()?)
    }
}

impl std::cmp::PartialEq for Token {
    fn eq(&self, other: &Self) -> bool {
        unsafe {
            cpp!([self as "const pxr::TfToken *",
                  other as "const pxr::TfToken *"]
                    -> bool as "bool" {
                return (*self) == (*other);
            })
        }
    }
}

fn from_c_str(value: &CStr) -> Token {
    let value_str = value.as_ptr() as *const std::os::raw::c_char;

    unsafe {
        cpp!([value_str as "const char *"] -> Token as "pxr::TfToken" {
            return pxr::TfToken(value_str);
        })
    }
}

impl std::convert::TryFrom<&str> for Token {
    type Error = pxr::Error;

    fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
        let value_cstring = std::ffi::CString::new(value)?;

        Ok(from_c_str(value_cstring.as_c_str()))
    }
}

impl std::fmt::Display for Token {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        if let Ok(text) = self.get_text() {
            write!(f, "{}", text)
        } else {
            write!(f, "<invalid token>")
        }
    }
}