1use kittycad_execution_plan_traits::{
2 InMemory, ListHeader, MemoryError, NumericPrimitive, ObjectHeader, Primitive, ReadMemory, Value,
3};
4use kittycad_modeling_cmds::{
5 ok_response::OkModelingCmdResponse, output::ImportedGeometry, shared::Point2d, websocket::ModelingBatch,
6};
7use serde::{Deserialize, Serialize};
8
9use crate::{
10 events::{Event, EventWriter, Severity},
11 sketch_types::{self},
12 Address, ApiRequest, BinaryArithmetic, Destination, ExecutionError, ImportFiles, Memory, Operand, Result,
13 UnaryArithmetic,
14};
15
16#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
18pub struct Instruction {
19 pub kind: InstructionKind,
21 pub source_range: Option<SourceRange>,
24}
25
26#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord, Deserialize, Serialize)]
28pub struct SourceRange(pub [usize; 2]);
29
30impl Instruction {
31 pub fn from_range(kind: InstructionKind, source_range: SourceRange) -> Self {
33 Self {
34 kind,
35 source_range: Some(source_range),
36 }
37 }
38 pub async fn execute(
40 self,
41 mem: &mut Memory,
42 session: &mut Option<kittycad_modeling_session::Session>,
43 events: &mut EventWriter,
44 batch_queue: &mut ModelingBatch,
45 ) -> Result<()> {
46 self.kind.execute(mem, session, events, batch_queue).await
47 }
48}
49
50impl From<InstructionKind> for Instruction {
51 fn from(kind: InstructionKind) -> Self {
52 Self {
53 kind,
54 source_range: None,
55 }
56 }
57}
58
59#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
61pub enum InstructionKind {
62 ApiRequest(ApiRequest),
64 ImportFiles(ImportFiles),
66 SetPrimitive {
68 address: Address,
70 value: Primitive,
72 },
73 SetValue {
75 address: Address,
77 value_parts: Vec<Primitive>,
79 },
80 AddrOfMember {
84 start: Operand,
86 member: Operand,
88 },
89 SetList {
106 start: Address,
108 elements: Vec<Vec<Primitive>>,
110 },
111 BinaryArithmetic {
113 arithmetic: BinaryArithmetic,
115 destination: Destination,
117 },
118 UnaryArithmetic {
120 arithmetic: UnaryArithmetic,
122 destination: Destination,
124 },
125 StackPush {
127 data: Vec<Primitive>,
129 },
130 StackPop {
132 destination: Option<Destination>,
135 },
136 StackExtend {
139 data: Vec<Primitive>,
141 },
142 Copy {
144 source: Address,
146 length: usize,
148 destination: Destination,
150 },
151 CopyLen {
158 source_range: Operand,
160 destination_range: Operand,
162 },
163 SketchGroupSet {
165 sketch_group: sketch_types::SketchGroup,
167 destination: usize,
169 },
170 SketchGroupAddSegment {
172 segment: InMemory,
174 source: usize,
177 destination: usize,
179 },
180 SketchGroupSetBasePath {
182 source: usize,
185 from: InMemory,
187 to: InMemory,
189 name: Option<InMemory>,
191 },
192 SketchGroupCopyFrom {
194 source: usize,
196 offset: usize,
198 length: usize,
200 destination: Destination,
202 },
203 SketchGroupGetLastPoint {
205 source: usize,
207 destination: Destination,
209 },
210 NoOp {
212 comment: String,
214 },
215 TransformImportFiles {
218 source_import_files_response: InMemory,
220 source_file_paths: InMemory,
222 destination: Destination,
224 },
225}
226
227impl InstructionKind {
228 pub async fn execute(
230 self,
231 mem: &mut Memory,
232 session: &mut Option<kittycad_modeling_session::Session>,
233 events: &mut EventWriter,
234 batch_queue: &mut ModelingBatch,
235 ) -> Result<()> {
236 match self {
237 Self::NoOp { comment: _ } => {}
238 Self::ApiRequest(req) => {
239 if let Some(session) = session {
240 req.execute(session, mem, events, batch_queue).await?;
241 } else {
242 return Err(ExecutionError::NoApiClient);
243 }
244 }
245 Self::ImportFiles(req) => {
246 req.execute(mem).await?;
247 }
248 Self::SetPrimitive { address, value } => {
249 events.push(Event {
250 text: format!("Writing output to address {address}"),
251 severity: crate::events::Severity::Info,
252 related_addresses: vec![address],
253 });
254 mem.set(address, value);
255 }
256 Self::Copy {
257 source,
258 length,
259 destination,
260 } => {
261 let sources: Vec<_> = (0..length).map(|i| source + i).collect();
262 events.push(Event {
264 text: "Reading value".to_owned(),
265 severity: Severity::Debug,
266 related_addresses: sources.clone(),
267 });
268
269 let data = sources
270 .iter()
271 .map(|i| mem.get(i).cloned().ok_or(ExecutionError::MemoryEmpty { addr: source }))
272 .collect::<Result<Vec<_>>>()?;
273 write_to_dst(data, destination, mem, events)?;
274 }
275 Self::SetValue { address, value_parts } => {
276 value_parts.into_iter().enumerate().for_each(|(i, part)| {
277 mem.set(address.offset(i), part);
278 });
279 }
280 Self::BinaryArithmetic {
281 arithmetic,
282 destination,
283 } => {
284 let out = arithmetic.calculate(mem, events)?;
285 match destination {
286 Destination::Address(addr) => {
287 events.push(Event {
288 text: format!("Writing output to address {addr}"),
289 severity: crate::events::Severity::Info,
290 related_addresses: vec![addr],
291 });
292 mem.set(addr, out);
293 }
294 Destination::StackPush => {
295 mem.stack.push(vec![out]);
296 }
297 Destination::StackExtend => {
298 mem.stack.extend(vec![out])?;
299 }
300 };
301 }
302 Self::UnaryArithmetic {
303 arithmetic,
304 destination,
305 } => {
306 let out = arithmetic.calculate(mem, events)?;
307 match destination {
308 Destination::Address(addr) => mem.set(addr, out),
309 Destination::StackPush => mem.stack.push(vec![out]),
310 Destination::StackExtend => mem.stack.extend(vec![out])?,
311 };
312 }
313 Self::SetList { start, elements } => {
314 let mut curr = start;
316 curr += 1;
317 let n = elements.len();
318 for element in elements {
319 mem.set(curr, element.len().into());
321 curr += 1;
322 for primitive in element {
324 mem.set(curr, primitive);
325 curr += 1
326 }
327 }
328 mem.set(
329 start,
330 Primitive::from(ListHeader {
331 count: n,
332 size: (curr - start) - 1,
333 }),
334 );
335 }
336 Self::AddrOfMember { start, member } => {
337 let member_primitive: Primitive = match member {
339 Operand::Literal(p) => p,
340 Operand::Reference(addr) => mem.get(&addr).ok_or(ExecutionError::MemoryEmpty { addr })?.clone(),
341 Operand::StackPop => mem.stack.pop_single()?,
342 };
343 events.push(Event {
344 text: format!("Property is '{member_primitive:?}'"),
345 severity: Severity::Debug,
346 related_addresses: Vec::new(),
347 });
348
349 events.push(Event {
351 text: format!("Resolving start address {start:?}"),
352 severity: Severity::Debug,
353 related_addresses: Vec::new(),
354 });
355 let start_address = match start {
356 Operand::Literal(Primitive::Address(a)) => a,
357 Operand::Literal(other) => {
358 return Err(ExecutionError::MemoryError(MemoryError::MemoryWrongType {
359 expected: "address",
360 actual: format!("{other:?}"),
361 }))
362 }
363 Operand::Reference(addr) => mem.get_primitive(&addr)?,
364 Operand::StackPop => {
365 let data = mem.stack.pop_single()?;
366 data.try_into()?
367 }
368 };
369 events.push(Event {
370 text: "Resolved start address".to_owned(),
371 severity: Severity::Debug,
372 related_addresses: vec![start_address],
373 });
374 let structure = mem
375 .get(&start_address)
376 .cloned()
377 .ok_or(ExecutionError::MemoryEmpty { addr: start_address })?;
378
379 let (index, member_display) = match structure {
381 Primitive::ListHeader(ListHeader { count, size: _ }) => match member_primitive {
383 Primitive::NumericValue(NumericPrimitive::Integer(i)) if i >= 0 => {
384 let i = i as usize;
385 if i < count {
387 events.push(Event {
388 text: format!("Property is index {i}"),
389 severity: Severity::Info,
390 related_addresses: Vec::new(),
391 });
392 (i, i.to_string())
393 } else {
394 return Err(ExecutionError::ListIndexOutOfBounds { count, index: i });
395 }
396 }
397 Primitive::NumericValue(NumericPrimitive::UInteger(i)) => {
398 if i < count {
400 events.push(Event {
401 text: format!("Property is index {i}"),
402 severity: Severity::Info,
403 related_addresses: Vec::new(),
404 });
405 (i, i.to_string())
406 } else {
407 return Err(ExecutionError::ListIndexOutOfBounds { count, index: i });
408 }
409 }
410 other_index => {
411 return Err(ExecutionError::MemoryError(MemoryError::MemoryWrongType {
412 expected: "uint",
413 actual: format!("{other_index:?}"),
414 }));
415 }
416 },
417 Primitive::ObjectHeader(ObjectHeader { properties, size: _ }) => match member_primitive {
419 Primitive::String(s) => {
420 if let Some(i) = properties.iter().position(|prop| prop == &s) {
422 events.push(Event {
423 text: format!("Property is index {i}"),
424 severity: Severity::Info,
425 related_addresses: Vec::new(),
426 });
427 (i, s.clone())
428 } else {
429 return Err(ExecutionError::UndefinedProperty {
430 property: s,
431 address: start_address,
432 });
433 }
434 }
435 other_index => {
436 return Err(ExecutionError::MemoryError(MemoryError::MemoryWrongType {
437 expected: "uint",
438 actual: format!("{other_index:?}"),
439 }))
440 }
441 },
442 other_structure => {
443 return Err(ExecutionError::MemoryError(MemoryError::MemoryWrongType {
444 expected: "list or object header",
445 actual: format!("{other_structure:?}"),
446 }))
447 }
448 };
449
450 let mut curr = start_address + 1;
452 for _ in 0..index {
453 let size_of_element: usize = match mem.get(&curr).ok_or(MemoryError::MemoryWrongSize)? {
454 Primitive::NumericValue(NumericPrimitive::UInteger(size)) => *size,
455 Primitive::ListHeader(ListHeader { count: _, size }) => *size,
456 Primitive::ObjectHeader(ObjectHeader { properties: _, size }) => *size,
457 other => {
458 return Err(ExecutionError::MemoryError(MemoryError::MemoryWrongType {
459 expected: "ListHeader, ObjectHeader, or usize",
460 actual: format!("{other:?}"),
461 }))
462 }
463 };
464 curr += size_of_element + 1;
465 }
466 events.push(Event {
467 text: format!("Member '{member_display}' begins at addr {curr}"),
468 severity: crate::events::Severity::Info,
469 related_addresses: vec![curr],
470 });
471 mem.stack.push(vec![Primitive::Address(curr)]);
475 }
476 Self::StackPush { data } => {
477 mem.stack.push(data);
478 }
479 Self::StackExtend { data } => {
480 mem.stack.extend(data)?;
481 }
482 Self::StackPop { destination } => {
483 let data = mem.stack.pop()?;
484 let Some(destination) = destination else { return Ok(()) };
485 write_to_dst(data, destination, mem, events)?;
486 }
487 Self::CopyLen {
488 source_range,
489 destination_range,
490 } => {
491 let src_addr = match source_range.eval(mem)? {
492 Primitive::Address(a) => a,
493 other => {
494 return Err(ExecutionError::MemoryError(MemoryError::MemoryWrongType {
495 expected: "address",
496 actual: format!("{other:?}"),
497 }))
498 }
499 };
500 let dst_addr = match destination_range.eval(mem)? {
501 Primitive::Address(a) => a,
502 other => {
503 return Err(ExecutionError::MemoryError(MemoryError::MemoryWrongType {
504 expected: "address",
505 actual: format!("{other:?}"),
506 }))
507 }
508 };
509
510 let len = match mem
511 .get(&src_addr)
512 .ok_or(ExecutionError::MemoryEmpty { addr: src_addr })?
513 {
514 Primitive::NumericValue(NumericPrimitive::UInteger(n)) => n,
515 Primitive::ObjectHeader(ObjectHeader { size, .. }) => size,
516 Primitive::ListHeader(ListHeader { size, .. }) => size,
517 other => {
518 return Err(ExecutionError::MemoryError(MemoryError::MemoryWrongType {
519 expected: "uint or obj/list header",
520 actual: format!("{other:?}"),
521 }))
522 }
523 };
524 for i in 0..*len {
525 let src = src_addr + i + 1;
526 let dst = dst_addr + i;
527 let val = mem.get(&src).ok_or(ExecutionError::MemoryEmpty { addr: src })?;
528 mem.set(dst, val.clone());
529 }
530 }
531 Self::SketchGroupSet {
532 sketch_group,
533 destination,
534 } => {
535 mem.sketch_group_set(sketch_group, destination)?;
536 }
537 Self::SketchGroupSetBasePath { source, from, to, name } => {
538 let mut sg = mem
539 .sketch_groups
540 .get(source)
541 .ok_or(ExecutionError::NoSketchGroup { index: source })?
542 .clone();
543 let from: Point2d<f64> = mem.get_in_memory(from, "from", events)?.0;
544 let to: Point2d<f64> = mem.get_in_memory(to, "to", events)?.0;
545 let name: String = match name {
546 Some(name) => mem.get_in_memory(name, "name", events)?.0,
547 None => String::new(),
548 };
549 let base_path = sketch_types::BasePath { from, to, name };
550 sg.path_first = base_path;
551 mem.sketch_group_set(sg, source)?;
552 }
553 Self::SketchGroupAddSegment {
554 segment,
555 source,
556 destination,
557 } => {
558 let mut sg = mem
559 .sketch_groups
560 .get(source)
561 .ok_or(ExecutionError::NoSketchGroup { index: source })?
562 .clone();
563 let (segment, _count) = mem.get_in_memory(segment, "segment", events)?;
564 sg.path_rest.push(segment);
565 mem.sketch_group_set(sg, destination)?;
566 }
567 Self::SketchGroupGetLastPoint { source, destination } => {
568 let sg = mem
569 .sketch_groups
570 .get(source)
571 .ok_or(ExecutionError::NoSketchGroup { index: source })?
572 .clone();
573 let p = sg.last_point();
574 write_to_dst(p.into_parts(), destination, mem, events)?;
575 }
576 Self::SketchGroupCopyFrom {
577 source,
578 offset,
579 length,
580 destination,
581 } => {
582 let sg = mem
583 .sketch_groups
584 .get(source)
585 .ok_or(ExecutionError::NoSketchGroup { index: source })?
586 .clone()
587 .into_parts();
588 let data = sg.into_iter().skip(offset).take(length).collect();
589 write_to_dst(data, destination, mem, events)?;
590 }
591 Self::TransformImportFiles {
592 source_import_files_response,
593 source_file_paths,
594 destination,
595 } => {
596 let resp: OkModelingCmdResponse = mem
597 .get_in_memory(source_import_files_response, "import files response", events)?
598 .0;
599 let OkModelingCmdResponse::ImportFiles(resp) = resp else {
600 return Err(ExecutionError::General {
601 reason: "Should have been ::ImportFiles variant".to_owned(),
602 });
603 };
604 let filepaths: Vec<String> = mem.get_in_memory(source_file_paths, "import files response", events)?.0;
605 let geometry = OkModelingCmdResponse::ImportedGeometry(ImportedGeometry {
606 id: resp.object_id,
607 value: filepaths,
608 });
609 write_to_dst(geometry.into_parts(), destination, mem, events)?;
610 }
611 }
612 Ok(())
613 }
614}
615
616fn write_to_dst(
617 data: Vec<Primitive>,
618 destination: Destination,
619 mem: &mut Memory,
620 events: &mut EventWriter,
621) -> std::result::Result<(), MemoryError> {
622 match destination {
623 Destination::Address(dst) => {
624 events.push(Event {
625 text: "Writing value".to_owned(),
626 severity: Severity::Debug,
627 related_addresses: (0..data.len()).map(|i| dst + i).collect(),
628 });
629 for (i, v) in data.into_iter().enumerate() {
630 mem.set(dst + i, v);
631 }
632 Ok(())
633 }
634 Destination::StackPush => {
635 mem.stack.push(data);
636 Ok(())
637 }
638 Destination::StackExtend => mem.stack.extend(data),
639 }
640}