0

This is my Clojure code:

(ns learnopengl.mesh-model
  (:import [org.lwjgl.assimp Assimp AINode AIMesh]))

(defn read-model
  "read a 3D model from a file"
  [path]
  (let [scene (Assimp/aiImportFile path (bit-or Assimp/aiProcess_Triangulate
                                                Assimp/aiProcess_FlipUVs))]
    (if (= scene nil)
      (println (Assimp/aiGetErrorString))
      (for [index (range (.mNumMeshes scene))]
        (do
          (println index)
          (AIMesh/create (.get (.mMeshes scene) index)))))))

which fails with:

0
Execution error (IllegalArgumentException) at java.nio.Buffer/createCapacityException (Buffer.java:290).
capacity < 0: (-576307456 < 0)

This happens inside AIMesh/create.

Believing I may have encountered a bug, I rewrote the snippet in Java:

import org.lwjgl.assimp.Assimp;
import org.lwjgl.assimp.AIScene;
import org.lwjgl.assimp.AIMesh;

class Test {
    public static void main(String[] args) {
        AIScene scene = Assimp.aiImportFile("backpack/backpack.obj", Assimp.aiProcess_Triangulate);
        if (scene == null) {
            System.out.println(Assimp.aiGetErrorString());
        } else {
            for (int i = 0; i < scene.mNumMeshes(); i++) {
                System.out.println(i);
                AIMesh mesh = AIMesh.create(scene.mMeshes().get(i));
            }
        }
    }
}

The Java code however runs smoothly. I don't understand why, since I believe the two snippets to be essentially the same. Can someone spot where I may have gone wrong in the Clojure code?

This is the 3D model I've been testing with.

Edit: This is my project.clj:

(defproject learnopengl "0.1.0-SNAPSHOT"
  :description "learnopengl book code"
  :url "learnopengl.com"
  :license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0"
            :url "https://www.eclipse.org/legal/epl-2.0/"}
  :dependencies [[org.clojure/clojure "1.11.1"]
                 [org.lwjgl/lwjgl "3.3.6"]
                 [org.lwjgl/lwjgl "3.3.6" :classifier "natives-linux"]
                 [org.lwjgl/lwjgl-opengl "3.3.6"]
                 [org.lwjgl/lwjgl-opengl "3.3.6" :classifier "natives-linux"]
                 [org.lwjgl/lwjgl-stb "3.3.6"]
                 [org.lwjgl/lwjgl-stb "3.3.6" :classifier "natives-linux"]
                 [org.lwjgl/lwjgl-glfw "3.3.6"]
                 [org.lwjgl/lwjgl-glfw "3.3.6" :classifier "natives-linux"]
                 [org.lwjgl/lwjgl-assimp "3.3.6"]
                 [org.lwjgl/lwjgl-assimp "3.3.6" :classifier "natives-linux"]
                 [org.joml/joml "1.10.8"]]
  :main ^:skip-aot learnopengl.core
  :target-path "target/%s"
  :profiles {:uberjar {:aot :all
                       :jvm-opts ["-Dclojure.compiler.direct-linking=true"]}})

Edit2:

This is my repository with the full code that breaks for me. read-model is defined in mesh-model.clj, and it is called an line 11 in core.clj.

Edit3:

This is the content from the /tmp/clojure-xxx.edn file produced for the crash, which contains the stack trace:

{:clojure.main/message
 "Execution error (IllegalArgumentException) at java.nio.Buffer/createCapacityException (Buffer.java:290).\ncapacity < 0: (-689151616 < 0)\n",
 :clojure.main/triage
 {:clojure.error/class java.lang.IllegalArgumentException,
  :clojure.error/line 290,
  :clojure.error/cause "capacity < 0: (-689151616 < 0)",
  :clojure.error/symbol java.nio.Buffer/createCapacityException,
  :clojure.error/source "Buffer.java",
  :clojure.error/phase :execution},
 :clojure.main/trace
 {:via
  [{:type clojure.lang.Compiler$CompilerException,
    :message
    "Syntax error macroexpanding at (learnopengl/core.clj:12:1).",
    :data
    {:clojure.error/phase :execution,
     :clojure.error/line 12,
     :clojure.error/column 1,
     :clojure.error/source "learnopengl/core.clj"},
    :at [clojure.lang.Compiler load "Compiler.java" 7665]}
   {:type java.lang.IllegalArgumentException,
    :message "capacity < 0: (-689151616 < 0)",
    :at [java.nio.Buffer createCapacityException "Buffer.java" 290]}],
  :trace
  [[java.nio.Buffer createCapacityException "Buffer.java" 290]
   [java.nio.Buffer <init> "Buffer.java" 253]
   [java.nio.ByteBuffer <init> "ByteBuffer.java" 316]
   [java.nio.ByteBuffer <init> "ByteBuffer.java" 324]
   [java.nio.MappedByteBuffer <init> "MappedByteBuffer.java" 113]
   [java.nio.DirectByteBuffer <init> "DirectByteBuffer.java" 107]
   [java.nio.ByteBuffer allocateDirect "ByteBuffer.java" 360]
   [org.lwjgl.system.Struct __create "Struct.java" 118]
   [org.lwjgl.assimp.AIMesh create "AIMesh.java" 487]
   [jdk.internal.reflect.DirectMethodHandleAccessor
    invoke
    "DirectMethodHandleAccessor.java"
    103]
   [java.lang.reflect.Method invoke "Method.java" 580]
   [clojure.lang.Reflector invokeMatchingMethod "Reflector.java" 167]
   [clojure.lang.Reflector invokeStaticMethod "Reflector.java" 332]
   [learnopengl.mesh_model$read_model$iter__332__336$fn__337$fn__338
    invoke
    "mesh_model.clj"
    66]
   [learnopengl.mesh_model$read_model$iter__332__336$fn__337
    invoke
    "mesh_model.clj"
    63]
   [clojure.lang.LazySeq sval "LazySeq.java" 42]
   [clojure.lang.LazySeq seq "LazySeq.java" 51]
   [clojure.lang.RT seq "RT.java" 535]
   [clojure.lang.RT countFrom "RT.java" 650]
   [clojure.lang.RT count "RT.java" 643]
   [learnopengl.core$eval351 invokeStatic "core.clj" 12]
   [learnopengl.core$eval351 invoke "core.clj" 12]
   [clojure.lang.Compiler eval "Compiler.java" 7194]
   [clojure.lang.Compiler load "Compiler.java" 7653]
   [clojure.lang.RT loadResourceScript "RT.java" 381]
   [clojure.lang.RT loadResourceScript "RT.java" 372]
   [clojure.lang.RT load "RT.java" 459]
   [clojure.lang.RT load "RT.java" 424]
   [clojure.core$load$fn__6908 invoke "core.clj" 6161]
   [clojure.core$load invokeStatic "core.clj" 6160]
   [clojure.core$load doInvoke "core.clj" 6144]
   [clojure.lang.RestFn invoke "RestFn.java" 408]
   [clojure.core$load_one invokeStatic "core.clj" 5933]
   [clojure.core$load_one invoke "core.clj" 5928]
   [clojure.core$load_lib$fn__6850 invoke "core.clj" 5975]
   [clojure.core$load_lib invokeStatic "core.clj" 5974]
   [clojure.core$load_lib doInvoke "core.clj" 5953]
   [clojure.lang.RestFn applyTo "RestFn.java" 142]
   [clojure.core$apply invokeStatic "core.clj" 669]
   [clojure.core$load_libs invokeStatic "core.clj" 6016]
   [clojure.core$load_libs doInvoke "core.clj" 6000]
   [clojure.lang.RestFn applyTo "RestFn.java" 137]
   [clojure.core$apply invokeStatic "core.clj" 669]
   [clojure.core$require invokeStatic "core.clj" 6038]
   [clojure.core$require doInvoke "core.clj" 6038]
   [clojure.lang.RestFn invoke "RestFn.java" 408]
   [user$eval140$fn__144 invoke "form-init15974239925031016013.clj" 1]
   [user$eval140 invokeStatic "form-init15974239925031016013.clj" 1]
   [user$eval140 invoke "form-init15974239925031016013.clj" 1]
   [clojure.lang.Compiler eval "Compiler.java" 7194]
   [clojure.lang.Compiler eval "Compiler.java" 7184]
   [clojure.lang.Compiler load "Compiler.java" 7653]
   [clojure.lang.Compiler loadFile "Compiler.java" 7591]
   [clojure.main$load_script invokeStatic "main.clj" 475]
   [clojure.main$init_opt invokeStatic "main.clj" 477]
   [clojure.main$init_opt invoke "main.clj" 477]
   [clojure.main$initialize invokeStatic "main.clj" 508]
   [clojure.main$null_opt invokeStatic "main.clj" 542]
   [clojure.main$null_opt invoke "main.clj" 539]
   [clojure.main$main invokeStatic "main.clj" 664]
   [clojure.main$main doInvoke "main.clj" 616]
   [clojure.lang.RestFn applyTo "RestFn.java" 137]
   [clojure.lang.Var applyTo "Var.java" 705]
   [clojure.main main "main.java" 40]],
  :cause "capacity < 0: (-689151616 < 0)",
  :phase :execution}}
8
  • What are your dependecies (from project.clj)? Commented Jul 3 at 17:03
  • Maybe unrelated: for is list comprehension, not the for-loop you are used to from other languages like Java.
    – cfrick
    Commented Jul 3 at 17:33
  • @MartinPůda: I've updated my question. Commented Jul 3 at 18:02
  • @cfrick: the for should yield a lazy sequence of meshes, That's what I want. Commented Jul 3 at 18:03
  • I'm unable to reproduce the error. I used "natives-windows" variants, called (read-model "backpack/backpack.obj"), the result is sequence of 79 AIMesh pointers. Commented Jul 3 at 18:17

1 Answer 1

1

As cfrick has mentioned in a comment under your question, for is unlikely to be suitable here unless the caller of read-model consumes the whole collection.

The code below is how I'd write it. Note that I also added ^String and ^long to avoid reflection.

(defn read-model
  "Reads a 3D model from a file and returns a vector of AIMesh pointers."
  [^String path]
  (if-let [scene (Assimp/aiImportFile path (bit-or Assimp/aiProcess_Triangulate
                                                   Assimp/aiProcess_FlipUVs))]
    (mapv (fn [^long index]
            (AIMesh/create (.get (.mMeshes scene) index)))
          (range (.mNumMeshes scene)))
    (throw (ex-info (Assimp/aiGetErrorString) {:path path}))))

9 Comments

Enter at least 15 characters
Just noticed that you've added a comment about a lazy collection. In that case, everything should work as is - no clue why it fails, impossible to tell without the full stack trace. I tried your code - it does work on my end.
Enter at least 15 characters
Enter at least 15 characters
This works. But why? Of course I was consuming the collection, otherwise the error would not have surfaced. Can you give me a hint on why AIMesh/create crashes in my code? I can't see why list comprehension is the problem.
Enter at least 15 characters
Enter at least 15 characters
Hm I'm having a vague guess of why my code fails: because of the produced lazy sequence, the AIMesh objects are created at a later point, not while running read-model. Is it possible that the scene or other data has already been garbage collected at that point?
Enter at least 15 characters
Enter at least 15 characters
As I said - your original code works on my end just fine. Your question doesn't have a stacktrace for that exception so no idea where exactly it could be coming from. And the fact that mesh objects are created outside of read-model shouldn't mean anything - read-model is not some special function after all, and nothing that has a reference pointing to it will be garbage collected.
Enter at least 15 characters
Enter at least 15 characters
I just noticed that you have added a repo to the question - it also works just fine for me.
Enter at least 15 characters
Enter at least 15 characters
|
Enter at least 15 characters

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.