Skip to content

Commit 40bea13

Browse files
authored
Merge pull request #16 from Wasabi375/fromMem
read file from memory
2 parents 363e2fd + 31e2f45 commit 40bea13

37 files changed

+722
-454
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,4 @@ Advantages:
5959
Disadvantages:
6060
- code needs to be ported from cpp to java
6161
- code needs to be maintained
62-
- a little slower compared to cpp when loading big meshes if not using assbin
62+
- a little slower compared to cpp when loading big meshes if not using assbin

src/main/kotlin/assimp/BaseImporter.kt

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ abstract class BaseImporter {
1919
/** Currently set progress handler. */
2020
var progress: ProgressHandler? = null
2121

22-
var ioSystem: IOSystem = ASSIMP.defaultIOSystem
2322
/** Returns whether the class can handle the format of the given file.
2423
*.
2524
* The implementation should be as quick as possible. A check for the file extension is enough. If no suitable
@@ -47,7 +46,8 @@ abstract class BaseImporter {
4746
* exception is thrown somewhere in internReadFile(), this function will catch it and transform it into a suitable
4847
* response to the caller.
4948
*/
50-
fun readFile(imp: Importer, pIOHandler: IOSystem = ioSystem, filePath: String): AiScene? {
49+
fun readFile(imp: Importer, ioHandler: IOSystem = ASSIMP.defaultIOSystem, filePath: String): AiScene? {
50+
5151
progress = imp.progressHandler
5252
assert(progress != null)
5353

@@ -59,7 +59,7 @@ abstract class BaseImporter {
5959

6060
// dispatch importing
6161
try {
62-
internReadFile(filePath, pIOHandler, sc)
62+
internReadFile(filePath, ioHandler, sc)
6363
} catch (err: Exception) {
6464
// extract error description
6565
err.printStackTrace()
@@ -119,9 +119,7 @@ abstract class BaseImporter {
119119
* @param file Path of the file to be imported.
120120
* @param scene The scene object to hold the imported data. Null is not a valid parameter.
121121
* */
122-
open fun internReadFile(file: String, ioSystem: IOSystem = this.ioSystem, scene: AiScene) = Unit//internReadFile(file.uri, scene)
123-
124-
//open fun internReadFile(file: URI, pIOHandler: IOSystem, scene: AiScene) = Unit
122+
open fun internReadFile(file: String, ioSystem: IOSystem, scene: AiScene) = Unit
125123

126124
companion object {
127125
/** Extract file extension from a string
Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,48 @@
11
package assimp
22

33
import java.io.*
4-
import java.nio.file.Files
4+
import java.nio.*
5+
import java.nio.channels.*
56
import java.nio.file.Path
67
import java.nio.file.Paths
78

89
class DefaultIOSystem : IOSystem {
910

10-
override fun exists(pFile: String) = File(pFile).exists()
11+
override fun exists(file: String) = File(file).exists()
1112

12-
override fun open(pFile: String): IOStream {
13+
override fun open(file: String): IOStream {
1314

14-
val path: Path = Paths.get(pFile)
15-
if (!Files.exists(path))
16-
throw IOException("File doesn't exist: $pFile")
15+
val path: Path = Paths.get(file)
16+
if (!exists(file))
17+
throw IOException("File doesn't exist: $file")
1718

18-
19-
return FileIOStream(path)
19+
return FileIOStream(path, this)
2020
}
2121

22-
class FileIOStream(override val path: Path) : IOStream {
22+
class FileIOStream(private val pathObject: Path, override val osSystem: DefaultIOSystem) : IOStream {
23+
24+
override val path: String
25+
get() = pathObject.toString()
2326

24-
override fun read() = FileInputStream(path.toFile())
27+
override fun read() = FileInputStream(file)
2528

26-
override fun reader() = BufferedReader(FileReader(path.toFile()))
29+
override fun reader() = BufferedReader(FileReader(file))
2730

2831
override val filename: String
29-
get() = path.fileName.toString()
32+
get() = pathObject.fileName.toString()
33+
34+
override val parentPath = pathObject.parent.toAbsolutePath().toString()
35+
36+
override val length: Long
37+
get() = file.length()
38+
39+
override fun readBytes(): ByteBuffer {
40+
RandomAccessFile(file, "r").channel.use {fileChannel ->
41+
return fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileChannel.size()).order(ByteOrder.nativeOrder())
42+
}
43+
}
3044

31-
override fun parentPath() = path.parent.toAbsolutePath().toString()
45+
val file: File
46+
get() = pathObject.toFile()
3247
}
3348
}

src/main/kotlin/assimp/IOStream.kt

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,30 @@ package assimp
22

33
import java.io.BufferedReader
44
import java.io.InputStream
5-
import java.nio.file.Path
5+
import java.nio.*
66

77
interface IOStream {
88

9-
val path : Path?
9+
val path : String
1010

1111
val filename: String
1212

1313
fun read() : InputStream
1414

1515
fun reader() : BufferedReader
1616

17-
fun parentPath() : String
17+
val parentPath : String
18+
19+
/**
20+
* length of the IOStream in bytes
21+
*/
22+
val length: Long
23+
24+
/**
25+
* reads the ioStream into a byte buffer.
26+
* The byte order of the buffer is be [ByteOrder.nativeOrder].
27+
*/
28+
fun readBytes(): ByteBuffer
29+
30+
val osSystem: IOSystem
1831
}

src/main/kotlin/assimp/IOSystem.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ import java.io.File
55
/** Interface to the file system. */
66
interface IOSystem {
77

8-
fun exists(pFile: String): Boolean
8+
fun exists(file: String): Boolean
99

10-
fun open(pFile : String): IOStream
10+
fun open(file : String): IOStream
1111

1212
fun close(ioStream: IOStream) = Unit // TODO unused ?
1313

14-
fun getOsSeperator(): String = File.separator
14+
val osSeparator: String get() = File.separator
1515
}

src/main/kotlin/assimp/Importer.kt

Lines changed: 69 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ import assimp.postProcess.ValidateDSProcess
4848
import glm_.BYTES
4949
import glm_.i
5050
import glm_.size
51-
import java.io.File
5251
import java.net.URI
5352
import java.net.URL
5453
import java.nio.ByteBuffer
@@ -303,7 +302,7 @@ constructor() {
303302
}
304303

305304
// Get file size for progress handler
306-
val fileSize = File(file).length().i
305+
val fileSize = ioSystem.open(file).length.i
307306

308307
// Dispatch the reading to the worker class for this format
309308
val desc = imp.info
@@ -373,8 +372,7 @@ constructor() {
373372
*
374373
* @note This is a straightforward way to decode models from memory buffers, but it doesn't handle model formats
375374
* that spread their data across multiple files or even directories. Examples include OBJ or MD3, which outsource
376-
* parts of their material info into external scripts. If you need full functionality, provide a custom IOSystem
377-
* to make Assimp find these files and use the regular readFile() API.
375+
* parts of their material info into external scripts. If you need full functionality you can use [readFilesFromMemory]
378376
*/
379377
fun readFileFromMemory(buffer: ByteBuffer, flags: Int, hint: String = ""): AiScene? {
380378
if (buffer.size == 0 || hint.length > MaxLenHint) {
@@ -385,16 +383,81 @@ constructor() {
385383
// prevent deletion of previous IOSystem
386384
val io = impl.ioSystem
387385

388-
ioHandler = MemoryIOSystem(buffer)
389-
390386
val fileName = "$AI_MEMORYIO_MAGIC_FILENAME.$hint"
387+
ioHandler = MemoryIOSystem(fileName to buffer)
388+
389+
readFile(fileName, flags)
390+
391+
impl.ioSystem = io
392+
393+
return impl.scene
394+
}
395+
396+
/**Reads the given file from a memory buffer and returns its contents if successful.
397+
*
398+
* If the call succeeds, the contents of the file are returned as a pointer to an AiScene object. The returned data
399+
* is intended to be read-only, the importer object keeps ownership of the data and will destroy it upon
400+
* destruction. If the import fails, null is returned.
401+
* A human-readable error description can be retrieved by accessing errorString. The previous scene will be deleted
402+
* during this call.
403+
* Calling this method doesn't affect the active IOSystem.
404+
*
405+
* @param fileName name of the base file
406+
* @param files a map containing the names and all the files required to read the scene (base file, materials,
407+
* textures, etc).
408+
* @param flags Optional post processing steps to be executed after a successful import. Provide a bitwise
409+
* combination of the AiPostProcessSteps flags. If you wish to inspect the imported scene first in order to
410+
* fine-tune your post-processing setup, consider to use applyPostProcessing().
411+
* @return A pointer to the imported data, null if the import failed.
412+
* The pointer to the scene remains in possession of the Importer instance. Use getOrphanedScene() to take
413+
* ownership of it.
414+
*/
415+
fun readFilesFromMemory(fileName: String, files: Map<String, ByteBuffer>, flags: Int): AiScene? {
416+
417+
for((name, buffer) in files) {
418+
if(buffer.size == 0){
419+
impl.errorString = "buffer $name is empty"
420+
return null
421+
}
422+
}
423+
if(!files.containsKey(fileName)){
424+
impl.errorString = "fileName ($fileName) not in files"
425+
return null
426+
}
427+
428+
val io = impl.ioSystem
429+
430+
ioHandler = MemoryIOSystem(files)
431+
391432
readFile(fileName, flags)
392433

393434
impl.ioSystem = io
394435

395436
return impl.scene
396437
}
397438

439+
/**Reads the given file from a memory buffer and returns its contents if successful.
440+
*
441+
* If the call succeeds, the contents of the file are returned as a pointer to an AiScene object. The returned data
442+
* is intended to be read-only, the importer object keeps ownership of the data and will destroy it upon
443+
* destruction. If the import fails, null is returned.
444+
* A human-readable error description can be retrieved by accessing errorString. The previous scene will be deleted
445+
* during this call.
446+
* Calling this method doesn't affect the active IOSystem.
447+
*
448+
* @param fileName name of the base file
449+
* @param flags Optional post processing steps to be executed after a successful import. Provide a bitwise
450+
* combination of the AiPostProcessSteps flags. If you wish to inspect the imported scene first in order to
451+
* fine-tune your post-processing setup, consider to use applyPostProcessing().
452+
* @param files the files required to read the scene (base file, materials, textures, etc) as a pair with their name.
453+
* @return A pointer to the imported data, null if the import failed.
454+
* The pointer to the scene remains in possession of the Importer instance. Use getOrphanedScene() to take
455+
* ownership of it.
456+
*/
457+
fun readFilesFromMemory(fileName: String, vararg files: Pair<String, ByteBuffer>, flags: Int = 0): AiScene? {
458+
return readFilesFromMemory(fileName, files.toMap(), flags)
459+
}
460+
398461
/** Apply post-processing to an already-imported scene.
399462
*
400463
* This is strictly equivalent to calling readFile() with the same flags. However, you can use this separate

src/main/kotlin/assimp/MemoryIOWrapper.kt

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,43 +3,66 @@ package assimp
33
import glm_.*
44
import java.io.*
55
import java.nio.*
6-
import java.nio.file.*
76
import java.io.IOException
87

98

109

1110
const val AI_MEMORYIO_MAGIC_FILENAME = "\$\$\$___magic___\$\$\$"
1211
const val AI_MEMORYIO_MAGIC_FILENAME_LENGTH = 17
1312

14-
class MemoryIOSystem(val buffer: ByteBuffer) : IOSystem{
13+
class MemoryIOSystem : IOSystem{
1514

16-
/** Tests for the existence of a file at the given path. */
17-
override fun exists(pFile: String): Boolean = pFile.startsWith(AI_MEMORYIO_MAGIC_FILENAME)
15+
val memoryFiles: MutableMap<String, ByteBuffer> = hashMapOf()
16+
17+
constructor(buffer: ByteBuffer) {
18+
memoryFiles[AI_MEMORYIO_MAGIC_FILENAME] = buffer
19+
}
20+
21+
constructor(vararg buffers: Pair<String, ByteBuffer>): this(buffers.toMap())
1822

23+
constructor(buffers: Map<String, ByteBuffer>){
24+
memoryFiles.putAll(buffers)
25+
}
26+
27+
/** Tests for the existence of a file at the given path. */
28+
override fun exists(file: String): Boolean = memoryFiles.containsKey(file)
1929

20-
override fun open(pFile: String): IOStream {
30+
override fun open(file: String): IOStream {
2131

22-
// TODO assimp originally returns null, but this would be against the current interface.
23-
// I guess it should never happen anyways so an exception is fine
24-
if(!pFile.startsWith(AI_MEMORYIO_MAGIC_FILENAME)) throw IOException("File does not exist! $pFile")
32+
val buffer = memoryFiles[file] ?: throw IOException("File does not exist! $file")
2533

26-
return MemoryIOStream(buffer, pFile)
34+
return MemoryIOStream(buffer, file, this)
2735
}
2836

29-
class MemoryIOStream(val buffer: ByteBuffer, override val filename: String = "") : IOStream {
37+
class MemoryIOStream(val buffer: ByteBuffer, override val path: String, override val osSystem: MemoryIOSystem) : IOStream {
3038

31-
override val path: Path?
32-
get() = null
39+
override val filename: String = run {
40+
val lastIndex = path.lastIndexOf(osSystem.osSeparator)
41+
path.substring(lastIndex + 1)
42+
}
3343

3444
override fun read(): InputStream {
35-
return ByteBufferBackedInputStream(buffer)
45+
return ByteBufferBackedInputStream(readBytes())
3646
}
3747

3848
override fun reader(): BufferedReader {
3949
return BufferedReader(InputStreamReader(read()))
4050
}
4151

42-
override fun parentPath(): String = ""
52+
override val parentPath: String = run {
53+
var parent = path.removeSuffix(filename)
54+
parent = parent.removeSuffix(osSystem.osSeparator)
55+
56+
// ensures that if the path starts with "./" it will always be at least that
57+
if(parent == ".") parent = ".${osSystem.osSeparator}"
58+
59+
parent
60+
}
61+
62+
override val length: Long
63+
get() = buffer.size.toLong()
64+
65+
override fun readBytes(): ByteBuffer = buffer.duplicate().order(ByteOrder.nativeOrder())
4366
}
4467
}
4568

@@ -63,7 +86,7 @@ private class ByteBufferBackedInputStream(val buf: ByteBuffer) : InputStream() {
6386
override fun read(): Int {
6487
return if (!buf.hasRemaining()) {
6588
-1
66-
} else (buf.get() and 0xFF).toInt()
89+
} else buf.get().toInt() and 0xFF
6790
}
6891

6992
/**

src/main/kotlin/assimp/format/X/XFileImporter.kt

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package assimp.format.X
22

33
import assimp.*
44
import assimp.AiFace
5-
import java.io.File
5+
import java.io.*
66

77
class XFileImporter : BaseImporter() {
88

@@ -24,16 +24,15 @@ class XFileImporter : BaseImporter() {
2424
}
2525

2626
override fun internReadFile(file: String, ioSystem: IOSystem, scene: AiScene) {
27-
// Read file into memory
28-
val file_ = File(file)
29-
if (!file_.canRead()) throw FileSystemException(file_, null, "Failed to open file \$pFile.")
27+
28+
val stream = ioSystem.open(file).read()
29+
val bytes = stream.readBytes()
3030

3131
// Get the file-size and validate it, throwing an exception when fails
32-
val fileSize = file_.length()
32+
val fileSize = bytes.size
3333

3434
if (fileSize < 16) throw Error("XFile is too small.")
3535

36-
val bytes = file_.readBytes()
3736
mBuffer = Pointer<Char>(Array<Char>(bytes.size, { i -> bytes[i].toChar() })) //Assuming every byte is a char.
3837
// parse the file into a temporary representation
3938
val parser = XFileParser(mBuffer)

0 commit comments

Comments
 (0)