forked from foxis/EasyRobot
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path.cursorrules
More file actions
321 lines (273 loc) · 14 KB
/
.cursorrules
File metadata and controls
321 lines (273 loc) · 14 KB
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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
# Project EasyRobot specific rules
- Main use-case : Robotics & Embedded Systems
- Target: Embedded systems - must be efficient, fast, and memory-conscious
- Precision - float32, or generic types (Numeric/NumericReal/NumericComplex)
- **CRITICAL: Use `github.com/chewxy/math32` package instead of `math` for all floating-point operations** (faster, optimized for float32, essential for embedded systems)
- As few allocations as possible (controlled buffer-preallocations, reuse)
- Low level algorithms and hot paths as optimized as possible <- consider possibility to segregate by architecture/type
- NEVER DO TYPE CASTS INSIDE HOT PATH FOR LOOPS!
- Do not use Docker when working on unit tests
- Use Python libraries via helper programs to verify results for math primitives
- Use testify in unit tests
- When working on EasyRobot project, modify only EasyRobot repository!
- **Before implementing new algorithms, ALWAYS check existing implementations in:**
- `x/math/dsp` - Digital Signal Processing operations
- `x/math/mat` - Matrix operations
- `x/math/vec` - Vector operations
- `x/math/tensor` - Tensor operations
- `x/math/primitive/generics` - Generic primitives
- Try not to roll out new algorithms if implementation already exists
- Reuse and extend existing functionality rather than duplicating
- prefer go run ./path instead of go build and then run
- prefer tmp folder for temporary code, program output and so on
- prefer x/workers for SHORT concurrent tasks if number of tasks is known or for general short parallelization tasks (don't use it forfunctions that should run a long time)
# General rules
- Prefer composition over inheritance
- Prefer `any` over `interface{}`
- Return early to reduce nesting
- `New*` functions should return types, not interfaces. Check instance conformance to interface.
- Prefer returning errors instead of panicking.
- Instead of `GetSomething()` prefer `Something` and accompanying `SetSomething`.
- Keep number of arguments low
- Write testable code: avoid hard-to-mock dependencies
- Each function does one thing (Single Responsibility)
- Do not refactor code unless explicitly asked
- Before implementing anything new, create a design/plan document explaining the feature at high level
- Before changing/adding new features - locate/find design document and update that document first to make it sense.
- Make sure features/modules/packages have high level design documents
- Use asyncrhonous programming when communicating with APIs or Serial, USB, etc.
- If a SPEC.md exists for specific module/package/feature, work on it before working on module/package/feature
- Follow Go Code Review Comments and Effective Go
- Maximum function length: 30 lines. Extract helper functions (Not a hard limit. do not split just for splitting sake)
- Package by feature/domain, not by layer
- Follow Domain Driven development but don't overcomplicate with abstractions for the sake of abstractions
- Accept interfaces, return structs
- Keep interfaces small (1-3 methods ideal)
- Error handling: never ignore errors, wrap with context
- Prefer table-driven tests
- Prefer Options pattern over builder or other patterns
- No naked returns except for very short functions
- Avoid package-level state; use dependency injection
- Constructor functions return interfaces when appropriate
- Use context.Context for cancellation and timeouts
- Prefer explicit over implicit
- Make zero values useful
- Avoid premature abstraction
- Makefile must have build, clean, test, cover commands
- If it is a service, then there has to be docker folder that uses `make build` to build in a build image and then lean final image (Does not apply to cli or libraries)
- Prefer Mermaid diagrams over just drawings
- Use Mockery for interface mocks
- Use Testify `requre` and `assert`
- Interface names must be verbs, e.g. "Reader", "Writer", "Searcher" according to go interface naming best practices.
# SOLID Principles (Comprehensive)
## Single Responsibility Principle (SRP)
- Each function/type/package should have one reason to change
- Each function does one thing (Single Responsibility)
- Maximum function length: 30 lines. Extract helper functions (Not a hard limit. do not split just for splitting sake)
- Separate concerns: I/O, business logic, data transformation
- Package by feature/domain, not by layer
## Open/Closed Principle (OCP)
- Open for extension, closed for modification
- Extend behavior without modifying existing code
- Use Options pattern over builder or other patterns
- Prefer composition over inheritance
- Use interfaces for extensibility
## Liskov Substitution Principle (LSP)
- Subtypes must be substitutable for their base types
- Implementations must honor interface contracts
- No breaking changes when substituting implementations
- Make zero values useful (structs should work with zero initialization)
## Interface Segregation Principle (ISP)
- Many specific interfaces > one general interface
- Keep interfaces small (1-3 methods ideal)
- Interface names must be verbs, e.g. "Reader", "Writer", "Searcher" according to go interface naming best practices
- Clients should not depend on methods they don't use
## Dependency Inversion Principle (DIP)
- Depend on abstractions, not concretions
- Accept interfaces, return structs
- Explicit dependencies in function/constructor signatures
- Avoid package-level state; use dependency injection
- Constructor functions return interfaces when appropriate
# Naming Conventions (Clear & Precise)
## General Naming Rules
- Name things precisely: no utils, helpers, managers as names
- Package names: don't stutter (http.HTTPServer → http.Server)
- Instead of `GetSomething()` prefer `Something` and accompanying `SetSomething`
- Interface names must be verbs (Reader, Writer, Searcher)
- Function names should be verbs (Read, Write, Process)
- Type names should be nouns (Reader, Config, Processor)
- Constants should be UPPER_CASE
- Variables should be camelCase or short names in local scope
## Avoid Anti-Patterns
- ❌ `utils`, `helpers`, `common`, `misc` - too vague
- ❌ `manager`, `handler`, `processor` - too generic
- ❌ `data`, `info`, `temp` - meaningless
- ✅ Use domain-specific names: `RobotController`, `SensorReader`, `PathPlanner`
## Function Naming
- `New*` functions should return types, not interfaces. Check instance conformance to interface.
- Keep number of arguments low
- Prefer Options pattern over builder or other patterns
# Cross-language principles
- If it is CRUD - prefer variable arguments for inserts (i.e. no CreateOne, CreateMany - the fewer similar functions the better)
- DRY but avoid premature abstraction
- Avoid writing multiple functions that do very similar thing
- Prefer using standard library instead of rolling your own algorithms
- Comments explain "why", not "what"
- Optimize for readability and maintainability over cleverness
## Enforcement Patterns
1. Proto files are the main source of truth for communication.
2. Services communicate via NATS using structs generated from protobuf schema.
Before generating code:
1. Identify single responsibility for each function
2. Check if function can be broken into smaller units
3. Verify all dependencies are passed explicitly
4. Ensure return values are strongly typed
5. Consider error handling paths
6. Take design/specs and/or implementation plan into account
When refactoring:
- Extract magic numbers to constants
- Extract complex conditions to named functions
- Replace parameter lists >3 with structs/objects
- Replace boolean parameters with enum/strategy pattern
## Language-Specific Gotchas
**Go:**
Avoid:
- Goroutine leaks (always ensure cleanup)
- Pointer receivers for consistency, value for immutability
- init() functions (makes testing hard)
- Package names: don't stutter (http.HTTPServer → http.Server)
- Manual if statements for min/max calculations when `min()`/`max()` functions are available (Go 1.21+)
Prefer:
- Early returns over deeply nested if-else
- errors.Is/As over type assertions
- sync.Pool for frequently allocated objects
- Concrete types in struct fields, interfaces in parameters
- Use `min()` and `max()` functions (Go 1.21+) instead of if statements for minimum/maximum calculations
- **Use `for i := range n` instead of `for i := 0; i < n; i++`** (better BCE, Go 1.22+)
# Embedded Systems Performance & BCE Optimizations
## Critical Performance Rules
- **Use `github.com/chewxy/math32` package instead of `math`** for all floating-point operations (faster, optimized for float32, essential for embedded systems)
- Low level algorithms and hot paths as optimized as possible (consider architecture/type-specific implementations)
- As few allocations as possible (controlled buffer-preallocations, reuse)
- **NEVER DO TYPE CASTS INSIDE HOT PATH FOR LOOPS!**
- Use sync.Pool for frequently allocated objects in hot paths
## Boundary Check Elimination (BCE) Strategies
### Loop Patterns
- **ALWAYS use `for i := range n` instead of `for i := 0; i < n; i++`**
- Better BCE opportunities
- Compiler can optimize better
- ~2-3% improvement consistently
### General BCE Patterns
**Reslicing for BCE Hints:**
- Always reslice slices to exact size before loops: `dst = dst[:size]`
- This tells the compiler exact bounds and eliminates bounds checks
- Works best with `for range` loops
**Flattening Multi-dimensional Data:**
- For contiguous operations, flatten to 1D when possible
- Reduces nested loop overhead and index calculations
- Better for element-wise operations
**Row/Column Slices for Strided Operations:**
- Create row/column slices for 2D strided data
- Reslice each slice to exact size for BCE
- Process each slice independently
**Loop Unrolling:**
- Unroll inner loops by 4-8 for hot paths
- Reduces loop overhead and improves instruction-level parallelism
- Handle remainder elements separately
**Recursive Decomposition:**
- For higher dimensions (3D+), decompose recursively
- Leverage optimized 1D/2D operations for lower dimensions
- Each recursive call gets fresh slice bounds (natural BCE)
### Memory Allocation Best Practices
**❌ NEVER use `make([]T, n)` for temporary arrays in hot paths**
- Creates heap allocations
- Increases GC pressure
- Slower than stack allocation
**✅ ALWAYS use stack arrays for small scratch space (< 1KB)**
- Use fixed-size arrays: `var buf [MAX_SIZE]T`
- Slice them: `buf[:actualSize]`
- Much faster, no GC pressure, better cache locality
**When to use `make()`:**
- Only for large arrays (> 1KB) that would overflow the stack
- Only when size is dynamically determined and too large for stack
- For arrays that need to escape the function scope
**Destination-Based Functions Pattern:**
- Accept optional destination slice parameter
- Use stack array if nil or too small
- Allows caller to reuse buffers or use stack allocation
### Platform-Specific Assembly
**✅ Recommended For:**
- Performance-critical hot paths where every nanosecond counts
- Large arrays where overhead is amortized
- Contiguous memory operations
- Simple operations that map well to SIMD instructions
- Platform-specific optimizations (e.g., amd64 with SSE/AVX)
**❌ Not Recommended For:**
- Generic code that needs to work across platforms
- Complex operations that don't map well to SIMD
- Strided operations (assembly doesn't handle strides well)
- Code maintainability is more important than performance
**Trade-offs:**
- ✅ Massive performance gains possible
- ✅ Zero allocations
- ❌ Architecture-specific (amd64 only)
- ❌ More complex to maintain
- ❌ Requires assembly knowledge
### Optimization Checklist (General Principles)
#### Loop Optimization:
1. ✅ Use `for range n` instead of `for i := 0; i < n; i++`
2. ✅ Reslice slices to exact size before loops
3. ✅ Flatten multi-dimensional data when possible
4. ✅ Unroll inner loops in hot paths
5. ✅ Use row/column slices for strided operations
#### Memory Management:
1. ✅ Use stack arrays for small scratch space (< 1KB)
2. ✅ Never use `make()` for temporary arrays in hot paths
3. ✅ Use destination-based functions for buffer reuse
4. ✅ Zero allocations in hot paths
5. ✅ Use sync.Pool for frequently allocated objects
#### Higher Dimensions:
1. ✅ Recursive decomposition to leverage optimized lower-dim operations
2. ✅ Use existing optimized Vec/Mat functions when available
3. ✅ Use stack arrays for shape/stride/indices
4. ✅ Avoid deep nesting - decompose early
# Context Management
When dealing with large files or datasets:
- NEVER copy entire large files into conversation
- Instead, create helper utilities to extract relevant portions
- Helper programs should be small, single-purpose tools
- Store helpers in tools/ or scripts/ directory
Helper program patterns:
1. Extract specific functions/types from source files
2. Parse and filter log files
3. Query structured data (JSON, CSV, etc.)
4. Generate summaries of large codebases
Example helper creation:
"""
If user needs to work with large.log file, create:
tools/extract_errors.go or tools/extract_errors.py
That reads the file and outputs only error lines with context.
Then run: docker run --rm -v $(pwd):/app -w /app golang:1.21 go run tools/extract_errors.go
"""
# Helper Program Requirements
When creating helper programs:
- Single file, standalone execution
- Clear flags/arguments for filtering
- Output to stdout for piping
- Include usage string
- Name pattern: {verb}_{target}.{ext}
* extract_function.py
* filter_logs.go
* parse_config.py
* summarize_module.go
# Docker Best Practices
Dockerfile structure:
- Use specific version tags, not 'latest'
- Multi-stage builds for compiled languages
- Minimal base images (alpine where possible)
- Non-root user for execution
- .dockerignore to exclude unnecessary files
For tests:
- Create docker-compose.test.yml if dependencies needed
- Use tmpfs mounts for test databases
- Clean up containers after tests (--rm flag)