Skip to content

A wrapper for Cursors that allows us to write virtual write_to_cursor#967

Open
tdavidovicNV wants to merge 1 commit into
mainfrom
dev/tdavidovic/add_anycursor
Open

A wrapper for Cursors that allows us to write virtual write_to_cursor#967
tdavidovicNV wants to merge 1 commit into
mainfrom
dev/tdavidovic/add_anycursor

Conversation

@tdavidovicNV
Copy link
Copy Markdown
Collaborator

@tdavidovicNV tdavidovicNV commented Apr 30, 2026

This wraps the common interface of BufferElementCursor and ShaderCursor, allowing us to a write one virtual interface that takes this generic cursor, rather than having to have two. This is a native code only, as Python can already do that.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added polymorphic cursor abstraction providing a unified interface for different cursor types
    • Added writable interface enabling custom cursor-writing implementations
  • Tests

    • Expanded test coverage for polymorphic cursor writing scenarios and type interactions

@tdavidovicNV tdavidovicNV requested a review from a team as a code owner April 30, 2026 14:16
@tdavidovicNV tdavidovicNV requested review from bmillsNV and removed request for a team April 30, 2026 14:16
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 30, 2026

📝 Walkthrough

Walkthrough

The PR introduces a new AnyCursor type-erased wrapper class that holds either a ShaderCursor or BufferElementCursor via std::variant, exposing a unified API for field navigation, type queries, and polymorphic value writing. The CursorWritable interface is added to support self-writing values, and the HasWriteToCursor concept is updated to accept non-templated write_to_cursor methods. Related template constraints in ShaderCursor::set<T> are aligned accordingly.

Changes

Cohort / File(s) Summary
Type-Erased Cursor Abstraction
src/sgl/device/any_cursor.h, src/sgl/device/any_cursor.cpp
New AnyCursor class wrapping ShaderCursor or BufferElementCursor with unified field navigation, type introspection, and set_* operations dispatching via std::visit. New CursorWritable interface with write_to_cursor(AnyCursor) method for polymorphic value writing.
Build Configuration
src/sgl/CMakeLists.txt
Added device/any_cursor.cpp and device/any_cursor.h to target sources compilation list.
Template Concept & Constraint Updates
src/sgl/device/cursor_utils.h, src/sgl/device/shader_cursor.h
Updated HasWriteToCursor concept to detect non-templated write_to_cursor(TCursor&) calls. Fixed ShaderCursor::set<T> template constraints to use HasWriteToCursor<T, ShaderCursor> instead of HasWriteToCursor<T, BufferElementCursor>.
Forward Declarations
src/sgl/device/fwd.h
Added forward declarations for AnyCursor and CursorWritable classes.
Test Coverage Expansion
tests/sgl/device/test_cursors.cpp
Extended tests to validate cursor-writing across ShaderCursor, BufferElementCursor, and AnyCursor with CursorWritable interface support; added static assertions for concept compatibility.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • Explicit shader object binding #812: Modifies ShaderCursor constructor explicitly and call sites; this PR updates ShaderCursor::set<T> overloads and copy/usage patterns, creating direct interaction between both changes.

Suggested reviewers

  • bmillsNV
  • skallweitNV

Poem

🐰 A cursor wears two faces now,
Wrapped in variant's mystic vow,
Through shader paths or buffer ways,
It navigates the data maze—
Type-erased, yet strong and clear,
The polymorph we all hold dear! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.08% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: introducing a wrapper (AnyCursor) for cursors that enables a virtual write_to_cursor interface.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dev/tdavidovic/add_anycursor

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 0/1 reviews remaining, refill in 60 minutes.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@ccummingsNV ccummingsNV left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we hold off on this until I've finished rewriting half the cursor code :)

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/sgl/device/test_cursors.cpp (1)

48-73: ⚡ Quick win

Use m_ prefix for newly added class members in test structs.

ConcreteCursorTestStruct and PolymorphicTestStruct introduce new members (f0, nested) that don’t follow the project member naming rule.

As per coding guidelines **/*.{cpp,cc,cxx,h,hpp,hh,hxx}: C++ classes use PascalCase, functions and variables use snake_case, member variables use m_ prefix.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/sgl/device/test_cursors.cpp` around lines 48 - 73, The new test structs
ConcreteCursorTestStruct and PolymorphicTestStruct violate the member-naming
rule—rename their members to use the m_ prefix (e.g., f0 -> m_f0, nested ->
m_nested), update their constructors/initializer lists to initialize m_f0 and
m_nested, and update all uses inside write_to_cursor (both overloads in
ConcreteCursorTestStruct and the override in PolymorphicTestStruct) and any
write_test_struct_fields calls to pass m_f0 and m_nested.data; keep parameter
names as you prefer but ensure all references use the new m_ member names.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/sgl/device/any_cursor.h`:
- Around line 23-121: Add Doxygen /// comments with `@param` and `@return` for the
new public API in AnyCursor and CursorWritable: document constructors
AnyCursor(ShaderCursor) and AnyCursor(BufferElementCursor), all query methods
(is_shader_cursor, is_buffer_element_cursor, is_valid, slang_type_layout,
to_string), conversion/accessors (as_shader_cursor, as_buffer_element_cursor),
subscript/find/has methods (operator[](string_view), operator[](uint32_t),
find_field, find_element, has_field, has_element), all setters (set_object,
set_buffer, set_buffer_view, set_texture, set_texture_view, set_sampler,
set_acceleration_structure, set_descriptor_handle, set_data,
set_cuda_tensor_view, set_pointer) and helpers
(_set_array/_set_scalar/_set_vector/_set_matrix) to describe parameters and
return values, and document CursorWritable::write_to_cursor (describe ownership
and whether cursor is copied or referenced); use /// style Doxygen comments
above each declaration and include `@param` for each parameter and `@return` where a
value is returned.

---

Nitpick comments:
In `@tests/sgl/device/test_cursors.cpp`:
- Around line 48-73: The new test structs ConcreteCursorTestStruct and
PolymorphicTestStruct violate the member-naming rule—rename their members to use
the m_ prefix (e.g., f0 -> m_f0, nested -> m_nested), update their
constructors/initializer lists to initialize m_f0 and m_nested, and update all
uses inside write_to_cursor (both overloads in ConcreteCursorTestStruct and the
override in PolymorphicTestStruct) and any write_test_struct_fields calls to
pass m_f0 and m_nested.data; keep parameter names as you prefer but ensure all
references use the new m_ member names.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7e1daf0e-59d5-4e2e-a460-d5b42a06afda

📥 Commits

Reviewing files that changed from the base of the PR and between 77ee373 and 22fb1b4.

📒 Files selected for processing (7)
  • src/sgl/CMakeLists.txt
  • src/sgl/device/any_cursor.cpp
  • src/sgl/device/any_cursor.h
  • src/sgl/device/cursor_utils.h
  • src/sgl/device/fwd.h
  • src/sgl/device/shader_cursor.h
  • tests/sgl/device/test_cursors.cpp

Comment on lines +23 to +121
AnyCursor(ShaderCursor cursor);
AnyCursor(BufferElementCursor cursor);

bool is_shader_cursor() const;
bool is_buffer_element_cursor() const;

ShaderCursor* as_shader_cursor();
const ShaderCursor* as_shader_cursor() const;
BufferElementCursor* as_buffer_element_cursor();
const BufferElementCursor* as_buffer_element_cursor() const;

bool is_valid() const;
std::string to_string() const;
slang::TypeLayoutReflection* slang_type_layout() const;

AnyCursor operator[](std::string_view name) const;
AnyCursor operator[](uint32_t index) const;

AnyCursor find_field(std::string_view name) const;
AnyCursor find_element(uint32_t index) const;

bool has_field(std::string_view name) const;
bool has_element(uint32_t index) const;

void set_object(const ref<ShaderObject>& object);

void set_buffer(const ref<Buffer>& buffer);
void set_buffer_view(const ref<BufferView>& buffer_view);
void set_texture(const ref<Texture>& texture);
void set_texture_view(const ref<TextureView>& texture_view);
void set_sampler(const ref<Sampler>& sampler);
void set_acceleration_structure(const ref<AccelerationStructure>& acceleration_structure);

void set_descriptor_handle(const DescriptorHandle& handle);

void set_data(const void* data, size_t size);
void set_cuda_tensor_view(const cuda::TensorView& tensor_view);
void set_pointer(uint64_t pointer_value);

template<typename T>
AnyCursor& operator=(const T& value)
{
set(value);
return *this;
}

template<typename T>
void set(const T& value)
{
using Value = std::decay_t<T>;
if constexpr (std::is_same_v<Value, ref<ShaderObject>>) {
set_object(value);
} else if constexpr (std::is_same_v<Value, ref<Buffer>>) {
set_buffer(value);
} else if constexpr (std::is_same_v<Value, ref<BufferView>>) {
set_buffer_view(value);
} else if constexpr (std::is_same_v<Value, ref<Texture>>) {
set_texture(value);
} else if constexpr (std::is_same_v<Value, ref<TextureView>>) {
set_texture_view(value);
} else if constexpr (std::is_same_v<Value, ref<Sampler>>) {
set_sampler(value);
} else if constexpr (std::is_same_v<Value, ref<AccelerationStructure>>) {
set_acceleration_structure(value);
} else if constexpr (std::is_same_v<Value, DescriptorHandle>) {
set_descriptor_handle(value);
} else if constexpr (std::is_same_v<Value, cuda::TensorView>) {
set_cuda_tensor_view(value);
} else if constexpr (HasWriteToCursor<T, AnyCursor>) {
value.write_to_cursor(*this);
} else {
std::visit(
[&](auto& cursor)
{
cursor.set(value);
},
m_cursor
);
}
}

void _set_array(const void* data, size_t size, TypeReflection::ScalarType cpu_scalar_type, size_t element_count);
void _set_scalar(const void* data, size_t size, TypeReflection::ScalarType cpu_scalar_type);
void _set_vector(const void* data, size_t size, TypeReflection::ScalarType cpu_scalar_type, int dimension);
void
_set_matrix(const void* data, size_t size, TypeReflection::ScalarType cpu_scalar_type, int rows, int cols);

private:
using CursorVariant = std::variant<ShaderCursor, BufferElementCursor>;

CursorVariant m_cursor;
};

/// Interface for values that can write themselves to any supported cursor type.
class SGL_API CursorWritable {
public:
virtual ~CursorWritable() = default;
virtual void write_to_cursor(AnyCursor cursor) const = 0;
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift

Add Doxygen @param/@return docs for the new public API methods.

AnyCursor/CursorWritable introduce a broad public surface, but per-method contract docs are mostly missing.

As per coding guidelines **/*.{cpp,cc,cxx,h,hpp,hh,hxx}: C++ documentation must use Doxygen format with /// comments and @param/@return tags.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sgl/device/any_cursor.h` around lines 23 - 121, Add Doxygen /// comments
with `@param` and `@return` for the new public API in AnyCursor and CursorWritable:
document constructors AnyCursor(ShaderCursor) and
AnyCursor(BufferElementCursor), all query methods (is_shader_cursor,
is_buffer_element_cursor, is_valid, slang_type_layout, to_string),
conversion/accessors (as_shader_cursor, as_buffer_element_cursor),
subscript/find/has methods (operator[](string_view), operator[](uint32_t),
find_field, find_element, has_field, has_element), all setters (set_object,
set_buffer, set_buffer_view, set_texture, set_texture_view, set_sampler,
set_acceleration_structure, set_descriptor_handle, set_data,
set_cuda_tensor_view, set_pointer) and helpers
(_set_array/_set_scalar/_set_vector/_set_matrix) to describe parameters and
return values, and document CursorWritable::write_to_cursor (describe ownership
and whether cursor is copied or referenced); use /// style Doxygen comments
above each declaration and include `@param` for each parameter and `@return` where a
value is returned.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants